JVM GC 机制
heap区是GC最频繁的,也是理解GC机制最重要的区域,了解JVM GC原理堆系统调优非常有用,若系统频繁发生FGC,整个系统的响应会非常卡顿,甚至崩溃。堆区由所有线程共享,在虚拟机启动时创建。堆区主要用于存放对象实例及数组,所有new出来的对象都存储在该区域。
1. java与c++回收机制
java是通过垃圾回收器处理垃圾,开发效率高,执行效率低。
c++的垃圾是需要手动处理的,开发效率低,执行效率高;但是,手动处理会出现忘记回收或者多次回收的问题,忘记回收会造成内存泄漏,回收多次会引起某些对象被非法引用。
2. 定位垃圾
2.1 什么是垃圾?
程序中没有任何引用的对象,是GC回收的对象。
2.2 垃圾查找算法
2.2.1 引用计数法(Reference Count)
heap中每一个对象都有一个引用计数,对象被创建并分配给某一变量时,引用计数为1;每被引用一次,计数加1,当某个对象的引用超过生命周期或者被设为新值时,引用次数减1。引用计数变为0时,就可以被当作垃圾被回收,若被回收的对象还引用其他对象时,这些对象的引用计数减1。
引用计数法,执行快,交织在程序运行中,适合于程序不被长时间打断的实时环境。但是,引用计数法不能定位到循环引用/递归引用的对象。
2.2.2 根可达算法(Root Searching)
以GC Roots为起点,向下搜索,所有走过的路径称为引用链,若某一对象没有引用链连接时,说明GC Root到此对象不可达,此对象就可以被当作垃圾被回收。
目前Hotspot采用的就是根可达算法。
被定义为GC Root的对象包括:
- JVM Stack(虚拟机栈中引用的对象)
- Native Method Stack(本地方法栈中引用的对象)
- Runtime constant pool(运行时常量池引用的对象)
- static references in method area (方法区中静态属性引用的对象)
3. 常见的垃圾回收算法
3.1 标记清除算法(Mark-Sweep)
从GC Root开始扫描,标记存活的对象,全部标记完之后,扫描整个区间,回收未被标记的对象。
优点,算法简单。
缺点,需要进行两次扫描,一次标记,一次清除,效率偏低,而且容易产生碎片。
适用场景,适用于存活对象较多的场景。
3.2 拷贝算法(Copying)
将堆栈一分为二,jvm生成的新对象放在一半空间中,GC回收时,把可达的对象copy到另一半空间,这一半空间剩下的直接回收。
优点,扫描一次,效率提高,不会产生碎片。
缺点,需要进行对象的移动复制,调整对象的引用,内存被一分为二,会造成空间的浪费。
适用场景,适用于存活对象较少的情况,适合伊甸区。
3.3 标记整理/压缩算法(Mark-Compact)
清除方式和Mark-Sweep一样,先标记存活的对象,然后清除未标记的,不同的时,在回收未标记的对象时,先把标记存活的对象移动整理到空闲空间,然后再进行回收。
优点,不会产生碎片。
缺点,需要扫描两次,还需要进行对象的移动,效率偏低。
4. JVM内存分代模型
目前,垃圾回收器有的使用分代模型,也有部分垃圾回收器使用不分代模型。
4.1 JVM 分代
一般新生代与老年代的默认占比是1:8。用参数 -XX:NewRatio=8
指定比例
4.1.1 新生代(Young generation)
大部分最新创建的对象会被分配在新生代,对新生代的回收,称为 MGC(minor GC)。
新生代被分为一个Eden区和两个Survicor区。
4.1.2 老年代(Tenured/Old generation)
对象在新生代中没有变为不可达,存活下来,就会被copy到老年代,老年代占用的空间比新生代大很多,一般发生GC要少很多,系统中要尽可能减少触发老年代的GC。对象从老年代消失的过程,成为FGC(full/major GC)。
4.1.3 永久代(Method Area)
Method Area是逻辑概念,jdk1.8之前,被称为Permanent generation,jdk1.8之后,被称为Meta generation。
4.2 对象分配过程
5. 常见的垃圾回收器
5.1 Serial
young generation 的垃圾回收器,是单线程的,串行的垃圾回收器,逻辑上物理上都分代,只会使用一个CPU或一条收集线程进行收集垃圾,而且,在收集垃圾的同时,其他工作线程都必须暂停,直到垃圾收集结束。
暂停的时间称为卡顿时间,STW(Stop The World)。
执行垃圾收集时,需要等待程序到达安全点(Safe Point)才能暂停,安全点的选定标准为,是否具有让程序长时间执行的特征。如产生方法调用/循环跳转等功能的指令时就会产生安全点。
使用场景,内存很小的情况下使用。
5.2 ParNew(Parallel new)
young generation 的垃圾回收器,逻辑上物理上都分代,是Serial回收器的多线程版本,是为了配合CMS的并行回收。
5.3 PS(Parallel Scavenage)
young generation 的垃圾回收器,逻辑上物理上都分代,使用的回收算法是copying,是并行的多线程收集器。
与ParNew的区别是,具有自适应的调节策略,通过参数-XX:+UseAdaptivePolicy
,打开这个参数,eden,survivor,old区的比例,晋升old区的年龄等细节都不需要手动设定,JVM会根据性能监测信息,动态调整这些信息。
使用场景,适用于吞吐量优先的场景,如科学计数法。
5.4 Serial Old
old generation 的垃圾回收器,逻辑上物理上都分代,是Serial收集器的老年代版本,也是单线程的的回收器,使用Mark-Compact算法。
5.5 PO(Parallel Old)
old generation 的垃圾回收器,逻辑上物理上都分代,是PS的老年代版本,JDK1.7/1.8 默认的回收器是PS+PO。
5.6 CMS(Concurrent Mark Sweep)
old generation 的垃圾回收器,逻辑上物理上都分代,是JDK1.4引入的。
收集过程:
- initial mark(初始标记),先标记GC Root,以及GC Root能直接关联到的对象,有STW,时间很短,速度很快。
- concurrent mark(并发标记),最耗时间,GC Root Tracing的过程,不产生STW。
- remark(重新标记),标记上一阶段因程序继续运作而导致标记变动的部分,会产生STW,比初始阶段时间稍长。
- concurrent sweep(并发清理),清理未标记的对象,这个阶段会产生新的垃圾,称为浮动垃圾,等待下一次GC清理。
优点
降低了STW的时间,CMS开启了并发回收的里程碑。
缺点 - 会产生浮动垃圾,在并发清理时,工作线程不会停止,导致清理过程中会有新的垃圾不断产生,档次GC不会回收,需等待下次GC,因此,在清理过程中,需要预留足够的内存空间给用户线程使用,不能等到完全被填满才进行收集,若预留空间无法满足程序需要 ,会出现Concurrent Mode Failure失败,这是JVM会启动Serial Old进行垃圾回收,STW时间会很长。解决方案是通过降低CMS阈值。
- 会产生碎片化问题,CMS采用的Mark-Sweep算法,所以会出现碎片化问题,若没有足够大的连续空间来分配当前对象,就会触发Full GC。解决方案,使用参数
-XX:+UseCompactAtFullCollection
使用场景,适用于响应优先的场景,如电商网站。
5.7 G1
逻辑上分代,物理上不分代。JDK1.9默认回收器,将堆划分为若干个Region(区域),这些Region分为E(Eden),S(Survivor),O(old),H(Humongous),H区用于存放巨型对象(一个对象占用的空间超过分区容量的50%以上),巨型对象会被直接分配在老年代,若一个H区装不下,G1会寻找连续的H分区来存储,为了找到连续的H区,有时候需要启动FGC。
特点
- 并发与并发收集,充分利用多核,多CPU的硬件优势,使用多个CPU缩短STW,同时在收集时,可以让java程序并发执行。
- 压缩空闲空间不会延长GC的暂停时间,采用Mark-Compact算法实现收集,两个Regiob之间采用的是Copying算法实现的,不会产生内存空间碎片。有利于程序长时间运行,分配大对象时也不会由于没有连续空间而提前触发下一次GC。
- 更易预测GC的暂停时间,可以建立STW预测模型,让使用者明确指定在一个长度为M毫秒的时间片段内,垃圾收集会尽可能地靠近这个时间。
G1收集过程
G1保留了YGC,同时针对老年代的收集采用了MIXGC,尽可能地避免FGC。
1). YGC,Eden区满了之后就会触发YGC,与其他回收期的YGC相同,把存活对象copy到survivor区或晋升到old gen。
在YGC的过程中,old gen的对象不回收,若GC Root可达的对象在old gen中,需要扫描整个老年代,会影响GC整体性能。所以,在CMS中,加入了Card Table的是由bitmap实现的,记录了old gen到y区的引用,若old区有对象引用y区,将它设为dirty,再次扫描时,只扫描dirty card,比扫描整个old gen,代价小很多。在G1中,采用的是Rset(Remembered Set)的数据结构,记录的是其他Regin中对象对本对象的引用,扫描时不需要扫描整个对战,只需要扫描Rset即可,从而减少了GC的工作量。
2). MixGC,G1中应尽量避免FGC,通过扩内存、提高CPU性能,或者降低Mixed GC触发的阈值,让Mixed GC 提早发生(默认为45%)。MixGC回收过程类似CMS回收的4个步骤,最后阶段是筛选回收的阶段,对各个Regin的回收价值和成本进行排序,根据用户期望的GC停顿时间来制定回收计划,这个阶段需要STW,提高收集效率。
使用场景 适用响应时间优先,不需要实现很高的吞吐量的场景,吞吐量比ParNew+CMS 少15%左右。
5.8 ZGC
不分代。采用的标记算法是ColoredPointers(颜色指针)。
关于ZGC后续再详细描述,目前了解不够。
6. 并发标记算法
6.1 三色标记算法
CMS 和 G1 采用的是三色标记算法。
对象分为3中类型,用3中颜色标记:
- black ,根对象,或自己以及子对象都被扫描。
- gray,对象本身被扫描,子对象还未被扫描。
- white,未被扫描的对象,即不可达对象,需要被回收的垃圾对象。
在并发标记的过程中会出现漏标的现象,即black指向white,gray没有指向white,解决漏标的方案: - CMS采用增量更新(incremental update)的方式,在插入的时候记录对象,若有white对象的引用被赋值到black对象,将black重新标记为gray。
- G1采用SATB(snapshor at the begining)的方式,关注删除的对象,删除时记录对象,push到G1的堆栈,与Rset配合使用,提高效率。
7. 常见的垃圾回收器组合
组合 | 设置参数 | 描述 |
---|---|---|
Serial + Serial Old | -XX:+UseSerialGC | 小型项目会用 |
ParNew+ Serial Old | -XX:+UseParNewGC | 很少用,某些版本已被弃用 |
ParNew + CMS + Serial Old | -XX:+UseConMarkSweepGC | 响应优先 |
PS + PO | -XX:+UseParallelGC | 1.8默认,吞吐量优先 |
PS + PO | -XX:+UseParallelOldGC | |
G1 | -XX:+UseG1GC | 1.9默认,响应优先,吞吐量要求不那么高 |
8. 垃圾回收器和内存大小的关系
内存大小 | 适用GC |
---|---|
< 100M | Serial |
100M - 10G | Parallel |
< 20G | CMS |
< 100G | G1 |
1T - 4T | ZGC |