1 什么是JVM
Java虚拟机JVM(Java Virtual Machine)是一个虚构的计算机,是通过在实际计算机上仿真模拟各种计算机功能来实现的。JVM屏蔽了平台相关信息,可将字节码文件解释为对应平台的机器码,实现Java程序跨平台执行。
2 JVM内存模型
JVM内存模型包括线程共享区【方法区、堆】、线程私有区【程序计数器、虚拟机栈、本地方法区】、直接内存。
2.1 方法区
方法区也称为永久代,用于存储类信息(类名、方法信息、字段信息等)、静态变量、常量以及编译后代码等。该部分信息是所有吸纳成功想的。 方法区中非常重要的一块是运行时常量池,主要用于存储编译时生成在字节码文件中的字面量、符号引用以及运行时生成的常量。
2.2 堆
堆内存用于存储创建的对象和数组,是一块线程共享的内存,也是垃圾回收的最主要区域。根据JVM的分代收集算法,堆内存可分为新生代和老年代。其中新生代分为Eden区、From Servivor区、To Servivor区。
- Eden区:新创建的对象会进入Eden区,当Eden区内存满后会触发MinorGC,对新生代执行一次垃圾回收。
- From Servivor区:上次GC留存的对象,也是下次GC的区域。
- To Servivor区:临时保留minorGC的有效对象。
2.3 虚拟机栈
单一线程调用方法是,会在虚拟机栈中烟如一个栈帧,一个栈帧中包含局部变量表、操作数栈、指向运行时常量池的引用、方法返回地址等信息。一个方法的调用到结束伴随着一个栈帧的创建入栈到销毁出栈。为确保一个线程的方法调用按顺序正常执行,需为各线程单独分配一个虚拟机栈。所以虚拟机栈是线程独立的。
2.4 本地方法区
本地方法区与虚拟机栈作用相似,不过本地方法区是为本地方法(Native Method)服务的,也是线程独立的。
2.5 程序计数器
程序计数器是线程执行字节码的行号指示器,用于记录虚拟机字节码指令地址。 同一进程中,多个线程通过时间片轮转获取CPU执行。在同一时间点CPU只会执行单一线程中的指令,因此,为确保各线程正常执行,JVM需为每一个线程分别分配一个程序计数器。也就是说程序计数器是线程私有的。同时,程序计数器也是JVM内存中唯一不会发生内存溢出的区域。
3 GC
程序运行过程中会不断产生对象消耗内存,为避免内存耗尽导致程序崩溃,需要及时将无用对象清除,回收内存。清除无用对象,回收内存的过程就是垃圾回收。
3.1 进行GC的内存区域
在JVM内存区域中,虚拟机栈、程序计数器、本地方法区都是线程独立的,其随单个线程而生灭,故无需进行垃圾回收。 方法区是存储类信息、静态变量和常量等数据的,存在内存耗尽的风险,垃圾收集器会将常量池和类卸载后的类信息进行回收,此处回收释放内存一般较少。堆内存作为对象创建存储的位置,会频繁创建对象,创建的大量对象会快速失去引用成为无用对象,故堆内存是GC执行的主要区域。
3.2 内存垃圾的确定方式
引用计数法:每个对象分配一个引用计数,每被引用一次则该计数+1,每被释放一次饮用则该计数-1,当计数为0时则该对象在GC阶段被判断为垃圾,可被垃圾收集器回收。但这种方式无法解决对象之间相互引用的问题,易造成内存泄漏。 可达性分析:自GC Roots开始向下搜索,搜索路径即为引用链。引用链中的对象为存活对象,其余则为垃圾,可被垃圾收集器回收。
GC Roots是一个对象的集合,主要包括虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法区中JNI引用的对象。
3.3 GC算法
常用的GC算法包括:标记-清除算法、复制算法、标记-整理算法、分代收集算法。
3.3.1 标记-清除算法
标记-清除算法分为标记和清除两个阶段,为每个对象分配一个标记位用于记录对象状态,标记阶段会遍历所有对象,对死亡对象进行标记,清除阶段再遍历一次,将死亡对象直接清除。
3.3.2 复制算法
复制算法是将内存分为a、b两块,每次只使用其中一块,当a内存满后将存活对象复制到b,然后将啊的内存占用清空。 复制算法相较于标记-清除算法,复制过程会将存活对象爱你个进行移动,避免了内存碎片的产生。但是无论如何,总有一块内存是空的,无法充分利用内存空间。
3.3.3 minorGC
minorGC是复制算法的一种。在JVM中,通常堆内存的新生代会采用minorGC算法。新创建的对象存储在新生代的Eden区中,当Eden区满后触发minorGC。其执行分为三步:复制 => 清除 => 互换。 复制:扫描Eden区和From Servivor区中存活的对象,将其年龄+1,若年龄较大(一般为15)则将该对象移至老年代,其余存活对象复制到To Servivor区(复制的过程也是整理的过程,在To Servivor区占用的是一块连续的内存区域)。 清除:将Eden区和From Servivor区清空。 互换:将From Servivor区和To Servivor区内存互换。
3.3.4 标记-整理算法
标记-整理算法同样需要遍历对象对死亡对象进行标记,然后清除死亡对象。与标记-清除算法不同的是,标记-整理算法会将所有存活对象移动至内存的一端,保证存活对象占用的是连续内存,然后将边界外的内存清除。 相较于标记-清除算法多了移动存活对象的过程,也因此解决了内存碎片的问题,同时也无需一片空闲的内存用于复制,有效的利用了内存。
3.3.5 分代收集算法
当前多数虚拟机采用分代收集算法,即对新生代、老年代采用不同的算法。根据区域内对象生命周期特点采用不同的算法。新生代中的对象大多生命周期短,能在GC过程中存活下来的对象较少,因此采用复制算法。老年代中的对象生命周期长,每次GC能回收的对象也较少,也没有额外的空间进行复制,故一般采用标记-整理算法。
4 垃圾收集器
4.1 Serial收集器(新生代)
Serial收集器是单线程的复制算法收集器,作用域新生代中。使用单个线程完成垃圾收集工作,过程中会暂停其他工作线程,直至完成垃圾回收。Client模式下的默认收集器。 -XX:+UseSerialGC 使用Serial+Serial Old模式进行垃圾回收
4.2 ParNew收集器(新生代)
ParNew与Serial相似,也是作用域新生代的复制算法收集器,同样也会暂停其他工作线程。不同的是ParNew采用多线程并行的方式来进行垃圾回收,如此可提高垃圾回收的效率。Server模式下的默认收集器。 -XX:+UserParNewGC 使用ParNew + Serial Old模式进行垃圾回收 -XX:ParallelGCThreads 用于设定执行垃圾回收的线程数,默认为CPU数
4.3 Parallel Scavenge收集器(新生代)
Parallel Scavenge与ParNew一样是作用于新生代的多线程复制算法收集器。不同的是,Parallel关注于CPU吞吐量(用户执行时间/总时间),可通过控制CPU吞吐量搞笑利用CPU。 -XX:+UseParallelGC 使用Parallel Scavenge + Serial Old模式进行回收 -XX:GCTimeRatio 设置用户执行时间占总时间比例,默认99,即1%的时间用于垃圾回收 -XX:MaxGCPauseMills 设计GC的最大停顿时间
4.4 Serial Old收集器(老年代)
Serial Old是单线程的标记-整理算法收集器,作用于老年代。在GC执行时会暂停其他工作线程直至完成垃圾回收。
4.5 Parallel Old收集器(老年代)
Parallel Old是多线程的标记-整理算法收集器,作用于老年代。在GC执行时会暂停其他工作线程直至完成垃圾回收。在多CPU的环境中与Parallel Scavenge搭配使用有很好的效果。 -XX:+UserParallelOldGC 使用Parallel Scavenge + Parallel Old模式进行垃圾回收
4.6 CMS(Concurrent Mark Sweep)收集器(老年代)
CMS(并发标记清除)是GC线程与工作线程交替并发执行的收集器。在整个GC过程中历经三次标记与一次清除,其中一次标记与清除是与工作线程并发执行的。
4.6.1 初始标记
初始标记阶段会暂停其他工作线程,标记GC Roots直接关联到的老年代对象(包括root直接关联和新生代直接关联到的对象),停顿时间段。
4.6.2 并发标记
并发标记阶段会与其他工作线程并发标记执行,根据初始标记中标记的对象进行向下搜索,将引用链中的所有对象进行标记,如此避免了将整个老年代进行扫描。
4.6.3 重新标记
并发标记阶段由于GC线程与其他工作线程并发执行,在此过程中仍会继续由对象创建或失去引用,因此需要在重新标记阶段对标记结果进一步修正补充。此次标记会暂停其他工作线程。
4.6.4 并发清除
并发清除阶段GC线程会与其他工作线程并发执行,直接清除垃圾对象。
4.6.5 优缺点
优点:在CMS进行垃圾回收的过程中,尽在初始标记、重新标记时进行短暂停顿 ,对系统工作线程正常执行影响小,效率更高。适合高并发场景。 缺点:本质上仍是标记清除算法,标记清除算法不可避免会产生内存碎片。垃圾清除不彻底,每次并发清除阶段都有可能同时产生了新垃圾。
|