JVM复习
基于JDK1.7的研究
一、Java内存区域与内存溢出
上图为jdk1.7的内存模型,下图借用下别人的1.8的图
-
程序计数器
指示当前线程所执行的字节码的行号,编译字节码执行的命令,进而程序才能一步步的执行下去。
-
Java虚拟机栈
线程私有,生命周期与线程相同。
每个方法在执行时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等。方法从调用到执行完成的过程中,就饿对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
**局部变量表:**存放了编译期间各基本数据类型、对象引用和returnAddress类型。其所需要的内存空间在编译期间完成分配,所以在方法运行期间是不会改变局部变量表的大小。
-
本地方法栈
与虚拟机栈类似,不同的是本地方法栈是为jvm调用Native方法服务。
-
Java堆
线程共享,存放对象实例。堆可以存在在物理上不连续,只要逻辑上连续就行。(GC在后面讲)
-
方法区
线程共享,用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
-
运行时常量池
方法区的一部分,存放Class文件在编译期间生产的各种字面常量和符号引用。Java中并非预置在Class文件中的常量池才会被放入常量池区。在运行期间也可以将新的常量放入该区域,如String类的intern()方法
-
直接内存
我理解的是为了方便JVM在Java堆和Native堆中来回复制数据设置的缓冲区。直接内存并不是JVM运行时数据区的一部分,这是一种基于通道和缓冲区的I/O方式。可以通过一个存储子啊Java堆中DirectByteBuffer对象作为这块内存的引用进行操作。
二、对象的创建
JVM接受到一个new的指令是,首先会去常量池中寻找该类的符号引用,然后检察这个符号引用代表的类是否已经加载、解析和初始化过。如果没有则先执行类加载过程。
-
空间划分
通过类接在的检查后,JVM为该对象分配内存(内存大小在类加载完后就确定了)。若Java堆的空间是规整连续的采用的分配算法是“指针碰撞”,直接移动与对象大小相同的距离指针分配内存。否则采用“空闲列表”分配,对于堆空间非连续的,用一张空闲记录表记录当前空闲的内存块,在为对象分配内存的时候,从空闲表中找到合适的空间进行分配,同时更新空闲表。
内存分配完后,JVM将分配到内存的空间全都初始化为零,最后再执行方法进行我们想要的初始化对象
-
对象的内存布局
在HotSpot虚拟机中,对象在内存中分为:对象头+实例数据+对齐填充。
对象头:包含了两部分信息,一部分用于存储对象自身的运行时数据(哈希吗、GC年龄代、锁状态、线程持有的锁等);另一部分是类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。若对象是个数组,对象头中还需有一块用于记录数组长度的数据。
实例数据:是对象真正存储的有效信息
对齐填充:对象的大小必须是8的整数倍,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
三、内存分配与回收
大多数情况下,对象在新生代Eden区中分配,当Eden去没有足够的空间进行分配的时候虚拟机会进行MinorGC
MinorGC:新生代GC,速度较快
FullGC/MajorGC:老年代GC,速度很慢
-
大对象直接进入老年代
-XX:PretenureSizeThreshold 令大于这个设置的值得对象直接进入老年代 ,Serial、ParNew有效。避免在新生代不停地复制
-
存活久的对象将进入老年代
对象计数器:对象在Eden区出生并经过第一次MinorGC后任然存活,移动进入Survivor空间,且对象年龄设置为1。对象在survivor区中每熬过一次MinorGC,年龄+1。当年龄增加到阈值(默认15),就进入老年代。
-
动态对象年龄判定
当Survivor空间中相同年龄所有对象大小>Survivor/2大小,年龄>=该年龄的对象就可以直接进入老年代,无需等到年龄阈值
-
空间分配担保
JDK 6 Update24之前:
在MinorGC之前,JVM会检查老年代的最大连续可用空间是否大于新生代所有对象空间。
大于:MinorGC是安全执行的
否则:JVM会去查看HandlePromotionFailure设置值是否允许担保失败。
允许:检查老年代最大可用的连续空间是否大于历次晋升老年代对象的平均大小
大于:尝试一次MinorGC
否则:进行FullGC
否则:进行FullGC
JDK 6 Update24之后:
只要老年代的连续空间大于新生代对象总大小或历次晋升的平均大小就会MinorGC,否则将进行FullGC