第四节 JVM运行模型

一、内存池与JVM内存模型

1、class文件在JVM中的运行图

第四节 JVM运行模型

 

2、JVM内存模型和JMM(Java Memory Model)的区别

(1)JMM:Java内存模型,用于多线程之间的线程通信。

(2)JVM内存模型:包含程序计数器、虚拟机栈、本地方法栈、堆、方法区。在JVM内部使用JMM将线程堆和堆之间的内存分开。

3、Java代码执行顺序

(1)首先将.java文件通过javac命令(javac.exe)编译成.class文件;

(2)接着通过java命令(java.exe)执行class文件,此时JVM中的类加载器子系统将class文件加载到内存中的class content中;

(3)然后方法区存储解析后的Class对象信息到ClassMirrorInstanceKlass中;

(4)JVM中的执行引擎找到入口main()方法并执行其中的内容;

(5)同时,程序计数器从main方法执行时就已经开始记录方法执行到哪一行命令;

(6)执行方法和创建对象会使用到虚拟机栈和堆以及底层可能会调用本地方法栈。

二、方法区

1、方法区、永久代和元空间的关系

(1)方法区是规范,永久代、元空间是具体实现,类似于接口和实现类的关系。

(2)永久代:用于存放类的元信息,JDK8以前使用的是永久代,永久代在堆区。

(3)元空间:用于存放类的元信息,JDK8及其以后元空间替代了永久代,元空间在直接内存中(OS内存)

(4)元空间取代永久代的原因:

a、gc算法的成熟;

b、OOM、动态生成、cglib的发展;

c、硬件的发展、系统越来也庞大。

2、元空间

(1)元空间最大和最小值:可以通过“java -XX:+PrintFlagsFinal -version |grep Metaspace”命令查看(MetaspaceSize)。

(2)元空间默认值:最大值默认值为4294901760B(约为4096M),最小值默认值是21807104B(约为20.8M)

(3)元空间调优:一般将元空间的最大值和最小值设置成相等,原因是为了防止内存抖动。一般为物理内存的1/32。

三、虚拟机栈

1、概述:JVM是软件模拟的虚拟机,基于栈运行,虚拟机栈是线程私有的,生命周期和线程相同;

2、数量:JVM中有多个线程,一个线程对应一个虚拟机栈;一个虚拟机栈有“方法调用次数”个栈帧;每个方法运行时,都会创建一个栈帧(Stack Frame);

3、栈帧存储的内容:局部变量表、操作数栈、动态链接、返回地址、附加信息。

a、栈帧作用:栈帧是为了解决作用域的问题,方便更好的处理方法。

b、局部变量表:存储基本类型数据(boolean, byte等)、对象引用类型(reference类型, 非对象本身, 指向对象的指针或其他)、返回值类型(执行一条字节码指令的地址)。

c、扩展:当运行一个方法时,此方法在栈帧中分配多大的局部变量表是完全确定的,在方法运行期间不会改变局部变量表的大小。

四、程序计数器(EIP:x64寄存器或RIP:x86寄存器)

1、概念:程序计数器是一块较小的内存空间,它主要记录当前程序执行到的命令的行号,可以看做是当前线程所执行的字节码的行号指示器。

2、扩展:如果线程正在执行一个java方法,这个程序计数器记录的正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,这个计数器值则为空。同时,此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

五、堆区

1、堆区模型

第四节 JVM运行模型

 

2、概述:在JVM启动时创建,唯一目的就是存放对象实例,几乎所有的对象都在堆区创建。线程共享内存区。

3、分类:新生代(Eden:From Survivor:To Survivor, 默认比例8:1:1)和老年代,默认比例是(1:2)。

4、最值:最小为物理内存的1/64,最大为物理内存1/4。

5、进入老年代的条件

(1)15次gc依然存活的对象:由于分代字段为4个字节,所以最大为15,不能调节成20;

(2)大对象:对象大小占Eden区的一半。Eden区的大小在运行时期动态调整的。

(3)空间分配担保:在发生新生代垃圾回收前(Minor GC),虚拟机会判断老年代最大可用内存是否大于新生代所有对象之和。如果此条件成立则分配,否则检查HandlePromotionFailure设置值是否允许担保失败。如果允许,则会继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小,如果大于,则进行一次Minor GC;如果小于或者HandlePromotionFailure不允许冒险,则这时需要进行一次Full GC。如果某次Minor GC对象突增,远远超过了历次平均值的话,导致了HandlePromotionFailure失败,那么JVM只好在失败后重新发起一次Full GC。尽管HandlePromotionFailure绕了很大一个圈子,但是大部分情况下是将此开关打开的,这样可以避免频繁的Full GC。

(4)动态年龄判断:针对Eden区和from区。虚拟机并不是永远的要求对象的年龄必须达到MaxTenuringThreshold才能进入老年代的,如果在Survivor空间中相同年龄所有对象的总和大于Survivor空间(From区或To区)的一半,年龄大于或等于该年龄的的对象可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

6、扩展:Java堆中可能划分出多个线程私有的分配缓冲区(TLAB, Thread Local Allocation Buffer)。

六、本地方法栈

概念:java调用c、C++的动态链接库、运行里面的函数需要的栈。随着socket的发展,JNI技术已经用的非常少了。

七、其他内容

1、this指针的赋值:执行引擎在执行带有操作数的字节码指令时,会有一个构造运行环境的过程。this指针就是在构造运行环境的过程中实现的。

2、Java虚拟机各个模块的关系

(1)虚拟机对方法区的引用:动态连接

(2)虚拟机对堆区的引用:局部变量

(3)方法区对堆区的引用:静态类型的属性

(4)堆区对方法区的引用:klass point

3、JVM运行方法的步骤(以main方法调用add方法为例)

第四节 JVM运行模型

 

(1)创建add方法的栈帧;

(2)在add方法的栈帧中保存main方法的字节码下一行程序计数器(用于执行完add方法时,程序计数器可以继续向下执行);

(3)main方法所在线程的局部表开始指针保存至add方法的栈帧中(此时正在创建Test对象,Test对象还是个不完整对象,JVM虚拟机赋值Test对象并将this传递下去);

(4)main方法所在线程的操作数栈开始指针保存至add方法的栈帧中(此时正在创建Test对象,Test对象还是个不完整对象,JVM虚拟机赋值Test对象并将this传递下去);

(5)将add方法的局部表指针赋值给线程的局部表指针;

(6)将add方法的操作数栈指针赋值给小城的操作数指针;

(7)准备好所有的参数后根据程序计数器的指示继续执行。

 

N、其他概念

1、Class相关概念

(1)class文件:硬盘上的.class文件;

(2)class Content:类加载器将.class文件硬盘上的.class文件读入内存中的那一块区域;

(3)Class对象:Class对象,如:Class<?> clazz = Test.class。在JVM中,真正获取到的是InstanceMirrorKlass实例。

(4)对象:Java对象,创建方式:new、反射

2、栈表相关

(1)局部变量表:存放局部变量的表

(2)操作数栈:存放操作数的栈

3、工具:visual VM、arthas(阿里)

 

问题:

1、1.8及其以后为什么用元空间代替永久代?

答:gc算法、OOM和动态生成和cglib、硬件的发展

2、为什么有了栈之后,还要有栈帧这个结构?栈帧存在的意义?

答:栈帧是针对于方法而言的,而局部变量是在不同的方法中,局部变量在不同的栈帧中,这样避免了作用域混淆的问题。

3、堆区的最小值和最大值为什么要调成一样大?

答:防止内存抖动。

 

习题:

1、测试一个栈帧占多少字节?思路:把栈大小调成160k(为什么调成160k?可以调成100k吗)