前言

相信很多人和我一样长期使用java编程,却很少关注过JVM底层实现,这很大程度上是因为JVM设计的很精巧,因此平时项目也很少遇到涉及JVM的问题。但是一方面出于对java底层技术的好奇,另一方面某些高并发,要对特定场景优化或者是排错的问题也迫切需要对JVM实现的了解,于是楼主这两天仔细拜读了《inside JVM》这本关于JVM的经典著作,对JVM的一些实现细节有了较为清楚的认识,将一些学习的体会和收获记录下来与各位有相同困扰的朋友分享。

本文将从JVM的几大核心技术切入:JVM内存管理、class文件格式、类装载、垃圾收集、多线程并发。需要注意的是因为Java是一个平台无关的技术,JVM在不同平台上必须有不同的实现,因此当年的Sun发布了一个JVM specification(Java 虚拟机规范)。任何团体或个人实现的JVM都必须遵照该规范才能正确的运行java程序。因此本文讨论的很多技术可能在不同的虚拟机上实现会有所不同,本文只是讨论一些通用的技术以及虚拟机规范定义的一些要求。

JVM内存管理

在Java虚拟机规范中,将JVM虚拟机的内存分成了如下图中运行时数据区几大区域

这几个区域分别是方法区,堆,Java栈,PC寄存器,本地方法栈。接下来我们就来详细认识下这些内存区域的作用。

首先要说的是堆,堆中存放的是所有在java程序运行过程中创建的对象,因为在java里,数组是以对象的形式存在,因此数组也是存放在堆中的。堆占据了JVM的大部分内存。因此也是Java的GC,垃圾收集器主要工作的目标区域。

接下来要说的是方法区,方法区里存储了所有类装载进来后和这个类相关的所有运行时需要的信息(如类的静态变量,常量,类的全局名称,方法信息等)。我们在后面介绍class文件的章节里会详细介绍class文件加载进来之后是如何将这些信息对应写入方法区的数据结构中的。

和前面介绍的两个区域是所有线程共享的不同,Java栈和本地方法栈以及PC寄存器都是线程独占的,也就是说每个线程都有一个java栈和PC寄存器或者本地方法栈(如果用到了本地方法的话)。

说到这里需要介绍一下本地方法,我们知道java是跨平台的,但是我们比如在需要读文件的时候,不用去关心将来是在哪个平台运行,只要调用FileInputStream把文件读入就可以了,不用调用底层操作系统的API函数,这是因为不同平台Java的API把所有这些与平台相关的操作都封装了起来提供了一个统一的Java编程接口。而Java的API正是通过调用一些本地方法(这些方法很多时候是一些编译后的可执行的C程序)来实现了这些功能。同时虽然Java实现了大部分平台都有的一些功能(如IO,多线程等),但是有些平台的一些功能是该平台特有的,提供Java虚拟机的厂商为了提供这些功能往往就以动态链接库的形式提供一些本地方法的调用来完善JVM在该平台的功能。至于如何去调用以及如何与本地方法通信(获取返回值等)就是具体JVM实现需要去做的事情。

说了这么多本地方法的内容,现在回到Java栈的部分,每个线程都有一个自己独立的Java栈,每次线程执行到一个新的方法时就在栈里面压入一个栈帧。帧里包含了方法里的局部变量,操作数栈以及帧数据区。这三种区域中局部变量很好理解,就是在方法作用范围内的变量,包括基本变量和对象的引用。理解操作数栈要先对JVM执行java程序的过程有所了解,JVM在装载进class文件后可能采用解释执行、即时编译执行、混合执行这三种方式来执行class文件中的JVM指令集。JVM指令集是一个4字节的指令集,就像汇编语言做相加操作需要先将两个数存入寄存器一样,JVM指令做数据相关的操作也要先将数据压入java栈里面的操作数栈才能进行。比如方法里将i变量和j变量相加赋值给z,JVM先将i压入操作数栈,再将j压入操作数栈,最后将结果写回局部变量表或者是对象的字段。至于帧数据区,是为了在方法执行过程中访问方法区的数据以及返回方法结果而用的。某个方法执行结束完之后如果是正常返回则会将返回结果压入上一个方法的操作数栈中,如果是异常退出且没有catch该异常则会运行到上一个方法继续抛出该异常。

本地方法和Java方法一样,只是Java栈是执行Java方法的线程申请的内存,而本地方法是执行本地方法而申请的内存。下面这张图显示了两者的关系。

最后程序计数器是为每个线程记录当前执行的字节码位置而设立的,线程切换时需要记录下当前执行到哪一步了以便该线程重新获取CPU执行时能继续正确执行。

顺便说一句,在java里面对象是通过引用来操作的,栈里面存储的引用,而堆里存储的对象。不同的JVM实现在引用的具体实现上可能有所不同,两种比较流行的方式分别是通过对象句柄引用和通过直接指针引用。JVM的GC也是通过引用来确定哪些对象可以回收。下图分别表示了两种引用的实现:

对比这两种引用实现,句柄池的方法在GC需要移动对象(消除内存碎片以存放大对象)时,只需要将句柄池中每个对象的指针地址修改即可。但是引用访问对象需要经过两个地址查找,降低了效率。直接指向对象的方式在需要移动对象时要将每个引用的地址都做修改,这相对直接修改句柄池来说要昂贵的多,但是因为一次寻址提高了效率。

细心的读者可能注意到不管采用什么方式,每个引用都有一个指向方法区里该类数据的指针。这是因为在java里面不像C++可以直接对内存对象做类型转换,Java类型转换前一定要做类型检查以保证这次转换是安全的以避免可能因此带来的程序崩溃。因此每个引用都有一个指向类型数据的指针。

本文花了很大篇幅介绍java栈的内容,是因为作者认为在这几个区域中,Java栈是最难理解的部分,希望读者能耐心读完,有什么问题也欢迎留言交流,最后为了加深对堆和栈存储哪些数据的理解,作者写了两个分别产生OutOfMemoryError和StackOverflowError的函数以帮助理解,oom函数在数组对象s中不停的添加数据,最后堆内存无法满足新的添加需求JVM就退出同时报出了OutOfMemoryError, stack()方法中有一个s的双精度局部变量,同时不停的递归调用自己,Java栈中就不停的压入新的方法栈,最后JVM退出并报出了StackOverflowError

Java代码:

package Experiment;

import java.util.ArrayList;

public class TestJVM {
public static void main(String[] args)
{
stackof();
//oom();
}
private static void oom()
{
ArrayList<Integer> s=new ArrayList<>();
while(true)
{
s.add(1);
}
}
private static void stackof()
{
double s;
stackof();
}
}

运行结果:

原文地址:http://www.cnblogs.com/developerY/p/3330811.html 转载请注明出处

深入JVM学习心得的更多相关文章

  1. JVM学习心得

    出处:http://blog.csdn.net/qq_16143915/article/details/51195438 一.JAVA内存管理与GC机制 Java在JVM所虚拟出的内存环境中执行,ja ...

  2. JVM学习心得—JVM内存模型(个人整理,请勿转载)

    一.运行时数据区域 线程私有的:程序计数器+虚拟机栈+本地方法栈 线程共享的:堆+方法区(运行时常量池)+直接内存(非运行时数据区的一部分) *JDK1.8后将方法区废除,新增元空间. 1.1 程序计 ...

  3. effective java 学习心得

    目的 记录一下最主要学习心得,不然凭我这种辣鸡记忆力分分钟就忘记白看了... 用静态工厂方法代替构造器的最主要好处 1.不必每次都创建新的对象 Boolean.valueOf Long.valueOf ...

  4. 我的MYSQL学习心得(一) 简单语法

    我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  5. 我的MYSQL学习心得(二) 数据类型宽度

    我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  6. 我的MYSQL学习心得(三) 查看字段长度

    我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  7. 我的MYSQL学习心得(四) 数据类型

    我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...

  8. 我的MYSQL学习心得(五) 运算符

    我的MYSQL学习心得(五) 运算符 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...

  9. 我的MYSQL学习心得(六) 函数

    我的MYSQL学习心得(六) 函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

随机推荐

  1. [转] libcurl异步方式使用总结(附流程图)

    文为转载,原文地址:libcurl异步方式使用总结 实习期间用到了libcurl来做HTTPS双向认证,用的是异步方式,简单总结一下. libcurl这个库的同步方式很简单,不做介绍,而异步方式很难理 ...

  2. maven 学习:为什么要使用maven,maven使用过程中的一些参数

    Maven是一个基于Java平台的项目构建工具. 设计的出发点: 在进行软件开发的过程中,无论什么项目,采用何种技术,使用何种编程语言,我们要重复相同的开发步骤:编码,编译,测试,生成文档,打包发布. ...

  3. java递归和反向递归

    1. 递归查询树tree结构有两种做法: 第一种,递归查询数据库结构, 第二种,一次性将数据库表中的所有数据查出来,然后再递归查出来的list集合, 第一种做法适合数据量较少的tree结构,因为要一直 ...

  4. hsweb 企业后台管理基础框架

    hsweb 详细介绍 业务功能 现在: 权限管理: 权限资源-角色-用户. 配置管理: kv结构,自定义配置.可通过此功能配置数据字典. 脚本管理: 动态脚本,支持javascript,groovy, ...

  5. Excel中使用VBA进行度分秒与十进制度的转换

    发现Excel的VBA功能真是批量处理的一把利刃,工作中小试牛刀了一把,将Excel中度分秒形式的坐标批量处理成十进制度形式,处理完后用于GIS展点制图. 原Excel数据如下: VBA代码如下: S ...

  6. Spring Boot 应用系列 4 -- Spring Boot 2 整合log4j2

    一.背景 1. log4j2传承于log4j和logback,它是目前性能最好的日志处理工具,有关它们的性能对比请看: 2. 除了性能好之外,log4j2有这么几个重要的新features: (1) ...

  7. Asp .Net Core网页数据爬取笔记

    突然要用到地区数据,想到以前用python的Scrapy框架写过一个爬虫,于是打算直接去国家统计局把最新的地区数据抓取回来.本想只需要copy一下以前的代码,就可以得到新鲜出炉的数据,谁知打开以前的项 ...

  8. c# async Task await Result 死锁

    最近项目数据量较大,使用 async Task异步增加执行效率 遇到问题,当前有2个计算非常耗时,现在需要你优化一下,这2个计算并行执行,2个计算执行完成后将2个结果sum返回给用户 当前我是这样实现 ...

  9. 【CronExpression表达式详解和案例】(转载)

    原文地址:https://www.cnblogs.com/pipi-changing/p/5697481.html   找了下Cron的资料,这篇作者写的比较清晰,转载记录一下,方便后面使用的时候在g ...

  10. Day 34 面试题