31、【对线面试官】G1垃圾收集器
要不这次来聊聊G1垃圾收集器?
CMS垃圾收集器的升级
G1垃圾收集器可以给你设定一个你希望Stop The Word停顿时间,G1垃圾收集器会根据这个时间尽量满足你
在前面我在介绍JM堆的时候,堆的内存分布是以「物理」空间进行隔离
在G1垃圾收集器的世界上,堆的划分不再是「物理」形式,而是以「逻辑」的形式进行划分
不过的「分代」概念在G1垃圾收集器的世界还是一样奏效的
比如说:新对象一般会分配到Eden区经过默认15次的 Minor GC新生代的对象如果还存活,会移交到老年代等等。
堆被划分了多个同等份的区域,在G1里每个区域叫做Region
G1中,还有一种叫 Humongous(大对象)区域,其实就是用来存储特别大的对象(大于 Region内存的一半)
一旦发现没有引用指向大对象,就可直接在年轻代的 Minor GC中被回收掉
之所以要将「堆空间」进行「细分」多个小的区域,是因为像以前的垃圾收集器都是对堆进行「物理」划分,如果堆空间(内存)大的时候,每次进行「垃圾回收」都需要对一整块大的区域进行回收,那收集的时间是不好控制的;而划分多个小区域之后,那对这些「小区域」回收就容易控制它的「收集时间」了
GC过程
在G1收集器中,可以主要分为有Minor GC( Young GC)和 Mixed GC,也有些特殊场景可能会发生 Full GC
Minor GC
G1的 Minor GC其实触发时机跟前面提到过的垃圾收集器都是一样的
等到Eden区满了之后,会触发 Minor GC。 Minor GCI同样也是会发生 Stop The World的
要补充说明的是:在G1的世界里,新生代和老年代所占堆的空间是没那么固定的(会动态根据「最大停顿时间」进行调整)
这块会给我们提供参数进行配置就好了
所以,动态地改变收集年轻代 Region的个数可以「控制」 Minor GCI的开销
Minor GC我认为可以简单分为为三个步骤:根扫描、更新&&处理RSet、复制对象
1)第一步应该很好理解,因为这跟之前CMS是类似的,可以理解为初始标记的过程
2)第二步就是处理RSet的信息并且扫描,将老年代对象持有年轻代对象的相关引用都加入到 GC Roots下,避免被回收掉
涉及到「Rset」的概念
(1)上ー次我们聊CMS回收过程的时候,同样讲到了 Minor GC,它是通过「卡表」( cart table)来避免全表扫描老年代的对象
(2)因为 Minor GC是回收年轻代的对象,但如果老年代有对象引用着年轻代,那这些被老年代引用的对象也不能回收掉
(3)同样的,在G1也有这种问题(毕竟是Minor GC)。CMS是卡表,而G1解决「跨代引用」的问题的存储一般叫做RSet
(4)只要记住,RSet这种存储在每个 Region都会有,它记录着「其他 Region引用了当前 Regiong的对象关系」
(5)对于年轻代的 Region,它的RSet只保存了来自老年代的引用(因为年轻代的没必要存储啊,自己都要做 Minor GC了
(6)而对于老年代的 Region来说,它的RSet也只会保存老年代对它的引用(在G1垃圾收集器,老年代回收之前,都会先对年轻代进行回收,所以没必要保存年轻代的引用)
3)第三步:把扫描之后存活的对象往「空的 Survivor区」或者老年代」存放,其他的Eden区进行清除
(1)这里要提下的是,在G1还有另一个名词,叫做CSet
(2)它的全称是 Collection Set,保存了一次GC中「将执行垃圾回收」的 Region。CSet中的所有存活对象都会被转移到别的可用 Region上
(3)在 Minor GC的最后,会处理下软引用、弱引用、 JNI Weak等引用,结束收集
总结
总结起来就是:扫描、处理跨 Region引用、收集至CSet、复制清除、处理引用
MixedGC过程
- 当堆空间的占用率达到一定阈值后会触发 Mixed GC(默认45%,由参数决定)
- Mixed GC会依赖「全局并发标记」统计后的 Region数据
- 「全局并发标记」它的过程跟CMS非常类型,步骤大概是:初始标记(STW)、并发标记、最终标记(ST)以及清理(ST)
- 说明: Mixed GC它一定会回收年轻代,并会采集部分老年代的Region进行回收的,所以它是一个混合GC
- 「初始标记」,
- 这个过程是「共用」了 Minor GC的 Stop The World(Mixed GC一定会发生 Minor GC),复用了「扫描 GC Roots的操作
- 在这个过程中,老年代和新生代都会扫
- 总的来说,「初始标记」这个过程还是比较快的,毕竟没有追溯遍历嘛
- 「并发标记」
- 这个阶段不会 Stop The World,GC线程与用户线程一起执行,GC线程负责收集各个 Region的存活对象信息
- 从 GC Roots往下追溯,査找整个堆存活的对象,比较耗时
- 「重新标记」
- 跟CMS又一样,标记那些在「并发标记」阶段发生变化的对象
- CMS在「重新标记」阶段,应该会重新扫描所有的线程栈和整个年轻代作为root,G1不是
- 在G1中解決「并发标记」阶段导致引用变更的问题,使用的是SATB算法
- 可以简单理解为:在GC开始的时候,它为存活的对象做了一次「快照」
- 在「并发阶段」时,把每一次发生引用关系变化时旧的引用值给记下来
- 然后在「重新标记」阶段只扫描着块「发生过变化」的引用,看有没有对象还是存活的,加入到「 GC Roots」上
- 不过SATB算法有个小的问题,就是:如果在开始时,G1就认为它是活的,那就在此次GC中不会对它回收,即便可能在「并发阶段」上对象已经变为了垃圾。
- 所以,G1也有可能会存在「浮动垃圾」
- 但是总的来说,对于G1而言,问题不大(毕竟它不是追求一次把所有的垃圾都清除掉,而是注重 Stop The Worlde时间)
- 「清理」
- 这个阶段也是会 Stop The World的,主要清点和重置标记状态,会根据「停顿预模型」(其实就是设定的停顿时间),来决定本次GC回收多少 Region
- 一般来说, Mixed GC会选定所有的年轻代 Region,部分「回收价值高」的老年代 Region(回收价值高其实就是垃圾多)进行采集
- 最后 Mixed GC进行清除还是通过「拷贝」/「复制」的方式去干的
- 所以在G1中,一次回收未必是将所有的垃圾进行回收的,G1会依据停顿时间做出选择 Region数量
什么时候发生full GC
- 如果在 Mixed GC中无法跟上用户线程分配内存的速度,导致老年代填满无法继续进行 Mixed GC,就又会降级到 serial oldGC来收集整个 GC heap
- 其实跟CMS是非常类似的都是因为空间不足
- 不过uGC这个场景相较于CMS还是很少的,毕竟G1没有像CMS「内存碎片」这种问题
G1垃圾收集器特点:
- 从原来的「物理」分代,变成现在的「逻辑」分代,将堆内存「逻辑」划分为多个Region
- 使用CSet来存储可回收Region的集合
- 使用RSet来处理跨代引用的问题(注意:RSet不保留 年轻代相关的引用关系)
- G1可简单分为:Minor GC 和Mixed GC以及Full GC
- 【Eden区满则触发】Minor GC 回收过程可简单分为:(STW) 扫描 GC Roots、更新&&处理Rset、复制清除
- 全局并发标记的过程跟CMS过程差不多:初始标记(STW)、并发标记、最终标记(STW)以及清理(STW)
- 【整堆空间占一定比例则触发】Mixed GC 依赖「全局并发标记」,得到CSet(可回收Region),就进行「复制清除」
- 使用SATB算法来处理「并发标记」阶段对象引用存在变更的问题
- 亮点&&重点:提供可停顿时间参数供用户设置(G1会尽量满足该停顿时间来调整 GC时回收Region的数量)
- R大描述G1原理的时候,他提到:从宏观的角度看G1,主要分为两块「全局并发标记」和「拷贝存活对象」