分享

Java划时代的收集器G1

 IT乐知 2020-04-15

Java发展至今有很多的收集器,而最具划时代的收集器就是G1了。

收集器发展过程

可以把收集器发展简单划分成四个阶段:

1、单线程阶段,对应收集器:Serial、Serial Old

2、并行阶段,多条收集器线程,对应收集器:ParNew、Parallel Scavenge、Parallel Old

3、并发阶段,收集器线程与用户线程同时运行,对应收集器:CMS(Concurrent Mark Sweep)

4、并行+并发+分区阶段,堆内存划分成多个小块进行收集,对应收集器:G1(Garbage First)

GC过程一定会发生STW(Stop The World),而一旦发生STW必然会影响用户使用,所以GC的发展都是在围绕减少STW时间这一目的。通过并行与并发已经极大的减少了STW的时间,但是STW的时间还是会因为各种原因不可控,而G1提供的一个最大功能就是可控的STW时间。

G1重要概念

一句话概括G1:通过把Java堆分成大小相等的多个独立区域,回收时计算出每个区域回收所获得的空间以及所需时间的经验值,根据记录两个值来判断哪个区域最具有回收价值,所以叫Garbage First(垃圾优先)。

所以有两个重要的概念:

Region(区域):G1收集器通过把Java堆分成一个个大小相等的Region,Region是G1回收的最小单元。

CSet(收集集合):GC过程记录的可被回收的Region的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、survivor空间、或者老年代。

RSet(Remembered Set 记忆集合):记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构 (谁引用了我的对象)。作用是不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。

在之前的解决跨代收集的方案中采用的是卡表(Card Table),简单的理解是它记录着老年代哪些区域存在对新生代对象的引用。在G1中由于不再是简单的新生代与老年代,每个Region中都可能被多个其他Region引用,所以用RSet来记录其他Region对当前Region的引用。

RSet示意图如下图:

RSet最简单的结果可以理解成”Hashtable<key,int[]>“结构,其中key对应的就是其他Region,而int[]对应其他Region的卡表。如上图Region2的RSet第一个元素对应Region,int[]中第二个与第四个元素值是1表示”脏“数据,也就是说Region1中的第2块与第4块区域存在对Region的引用,同理也可以得出Region3的第1块与第4块存在对Region2的引用。

在对Region2进行标记的时候,就只通过RSet找到对应哪些内存区域存在对Region2的引用,而不用进行整个堆的扫描。

Humongous regions:用来存放大于标准的Region内存50%的大对象区域,如果有些对象大于整个Region就会去找连续的Region保存,如果没有就会触发GC。

G1的特点

G1收集器与之前的收集器最大的不同就在于堆内存的划分,之前的收集器只区分新生代与老年代,而G1收集器则是把堆内存划分成多个独立的Region。

对比如下图:

在上图中G1的Java堆中每个Region都有一个身份,每个Region有可能是eden、survivor、old,但是他们的身份仅仅是逻辑上的,是可以变化的,G1可以根据情况动态的调整各种Region的数量,通过控制回收的Region数量来控制STW的时间,以达到STW时间的可控制。

G1分代算法

再回顾下之前的收集器总览图,如下图:

因为每种收集器都是对“标记-复制”、“标记-清除”、“标记-清理”其中一种算法的具体实现,所以基本上之前的收集器要么适用于新生代的要么适用于老年代。而G1由于独特的Java堆划分方式实现了一种算法适用整个Java堆。

G1收集过程

虽然G1收集器把Java堆化整为零成一个个Region,但是也不会进行所有Region进行收集,G1也分成了两种收集模式,两种模式如下:

Young GC: CSet就是所有年轻代里面的Region;

Mixed GC: CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的老年代Region;

Young GC过程:

阶段1:根扫描,静态和本地对象被扫描;

阶段2:更新RS,处理dirty card队列更新RS;

阶段3:处理RS,检测从年轻代指向老年代的对象;

阶段4:对象拷贝,拷贝存活的对象到survivorl/old区域;

阶段5:处理引用队列,软引用,弱引用,虚引用处理;

Mixed GC过程:

1、全局并发标记(global concurrent marking)

2、拷贝存活对象(evacuation)

全局并发标记包括5个步骤:

1、初始标记(initial mark,STW):标记了从GCRoot开始直接可达的对象。

2、根区域扫描(root region scan):G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。

3、并发标记(Concurrent Marking):G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断。

4、重新标记(Remark,STW):该阶段是 STW回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。

5、清除垃圾(Cleanup):在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

总结

G1的GC过程会在Young GC和Mixed GC之间不断地切换运行,同时定期地做全局并发标记,在实在赶不上对象创建速度的情况下使用Full GC(Serial GC)。

示意图如下:

最终整理梳理下流程:首先G1把Java分成多个Region,每个Region中存放着RSet,G1收集的时候扫描其他区域的GC Roots(比如方法栈中的临时变量),然后由GC Roots找到直连的对象,然后找到RSet中引用的对象,以这两类对象进行堆的引用标记。标记完成后把新生代中所有的Region放到CSet,有时会触发全局标记然后选出部分收集效率高的老年代Region加入到Cset中区,然后清理CSet的Region,完成清理。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多