JVM G1垃圾接纳器详细剖析

打印 上一主题 下一主题

主题 1004|帖子 1004|积分 3012

G1内存结构

Garbage First(简称G1)收集器摒弃了传统垃圾收集器的严格的内存分别,而是接纳了基于Region的内存结构形式和局部接纳的设计思路。

G1垃圾收集器把Java堆分别为2048个大小相等的独立的Region,每个Region大小取值范围为1-32MB,且必须为2的N次幂参数,可以通过-XX:G1HeapRegionSize设定,-XX:G1HeapRegionSize默认为0,此时Region的大小接纳Java堆大小/2048的计算公式来确定,取值为最靠近的2的N次幂数值。
Region有四种:Eden、survivor、Old、Humongous(用于存储超过1.5个region的大对象),空白表示未使用区域。
G1的特点

分代收集: G1依然属于分代垃圾接纳器,它会区分年轻代和老年代,年轻代依然有Eden和Survivor,但是从堆的结构上看,年轻代和老年 代不再物理隔离,而是逻辑上的概念;不要求整个eden、survivor区一连的,也不再坚持固定大小和数量。
制止内存碎片: 有利于程序长时间运行,分配大对象时不会由于无法找到一连的内存空间而提前触发下一次GC。
可预测的停顿时间: G1除了寻求低停顿外,还能创建可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾接纳上的时间不得超过N毫秒,可以通过参数-XX:MaxGCPauseMillis设置。 由于分区的原因, G1跟踪各个Region里面的垃圾堆积的价值大小(接纳所获得的空间大小以及接纳所需要的时间),在配景维护一个优先列表,每次根据答应的收集时间,优先接纳价值最大的Region。保证G1收集器在有限的时间内获取尽可能高的收集效率。
G1的接纳阶段分别

年轻代接纳(Young GC):仅接纳年轻代(Eden和Survivor区)。年轻代通常包罗新创建的对象,这些对象更有可能在短时间内变成垃圾。Young GC的执行过程相对较快,由于它只涉及新生代中对象的扫描和接纳。
在Young GC过程中,Eden区和Survivor区的存活对象会被复制到另一个Survivor区大概晋升到老年代。这个过程是Stop-The-World(STW)的,意味着在接纳过程中,应用程序的所有线程都会被暂停。但是,由于新生代中的可接纳对象通常较少,因此这个暂停时间通常较短,对应用程序的性能影响也较小。
并发标志周期(Concurrent Marking Cycle):包罗初始标志(Initial Mark)、并发标志(Concurrent Marking)、最终标志(Final Marking)和清理(Cleanup)阶段。并发标志周期完成后,才会触发混合接纳(Mixed GC),该阶段会接纳部分年轻代和老年代Region。


  • Mixed GC
    当老年代占用率到达参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,接纳所有的Eden区、Survivor区和部分Old区(根据盼望的GC停顿时间和接纳收益确定Old区垃圾收集的优先次序)以及大对象区。
  • Full GC
    在Mixed GC 接纳的内存不敷的时候触发Full GC,接纳全堆。
Young GC的接纳计谋

Young GC过程中,如果老年代中有对象引用了年轻代中的对象,那么这些年轻代对象在接纳过程中不应被错误地扫除。Young GC面临一个挑衅:如何识别和处置惩罚老年代对象对年轻代对象的引用?
厘清概念

记忆集(RememberedSet)
RSet详细记载了如老年代对象引用年轻代对象的关系。在年轻代接纳时,RSet中的对象被临时加入到GC Root中,这样垃圾接纳器就可以或许根据引用链准确地判断哪些对象需要接纳,哪些对象由于被老年代引用而需要保留。

为了进一步优化内存使用,G1将每个区域中的内存按照肯定大小分别成多个卡页(512B),并为每个卡页分配一个编号。在RSet中,不再记载单个对象的引用关系,而是记载对卡页的引用关系。这样,即使一个卡页中包罗多个对象,也只需要记载一次卡页的引用,从而显著减少了内存开销。

RSet是一种points-into结构(谁引用了我),实际实现是一个Hash Table,Key是别的Region的起始地址,Value是一个聚集,里面的元素是卡表(Card Table)的Index。
卡表(Card Table)
G1为所有的Region维护了一张全局的卡表(Card Table),它的核心作用是记载跨代引用(老年代对象引用年轻代对象),是一种points-out(我引用了谁)的结构。卡表的每个卡(Card)通常是一个字节,标志为1表示“脏”,即该卡对应的内存块(512B)有老年代对象引用了年轻代对象。


  • 写屏障(Write Barrier):当发生跨代引用时,写屏障会触发卡表的标志操纵,将对应的卡标志为“脏”。
  • RSet的更新:G1的每个Region的RSet通过卡表的脏卡信息,间接记载哪些Region的哪些卡页引用了当前Region的对象。记忆集的粒度更精细,制止全量扫描老年代(制止全量扫描在卡表中被标志为脏卡的卡页中的所有对象)。
Young GC的详细步骤

Young GC过程是Stop-The-World(STW)的。
1.GC Root扫描: 暂停应用线程,标志GC Root直达的对象。
2.处置惩罚脏卡:查抄卡表中的脏卡,更新记忆集。
3.标志存活对象: 基于GC Root和记忆集的信息,G1会递归遍历所有被引用的对象,标志为存活,未被标志的对象即为垃圾。
4.选择接纳聚集: 根据停顿时间目的(-XX:MaxGCPauseMillis),动态计算哪些Region接纳性价比最高,优先接纳。
Mixed GC的详细步骤

上文提到,并发标志周期(Concurrent Marking Cycle)完成后,才会触发Mixed GC,并发标志周期如下所示。

1.初始标志(Initial Mark,STW)
这一阶段标志了从GC Root开始直接可达的对象。
G1垃圾接纳器接纳了三色标志法来识别对象的状态。


  • 黑色:当前对象不仅在GC Root的引用链上,而且它所引用的所有对象也已经被标志为存活。在位图中,相应的bit位被标识为1。
  • 灰色:当前对象在GC Root的引用链上,但其引用的其他对象尚未被标志。灰色对象不会直接体现在位图中,而是被放入一个专门的队列中,等候后续处置惩罚。
  • 白色:当前对象不在GC Root的引用链上,可被接纳。在位图中,相应的bit位被标识为0。

2.并发标志(Concurrent Marking)
这一阶段处置惩罚在初始标志阶段中放入灰色队列中的对象。
示例如上图和下图所示:从灰色队列中提取出对象B,并对其关联的A和C对象举行标志,A对象并未引用其他任何对象,因此可以立即标志为黑色,C对象引用了另一个对象E,因此C对象被标志为灰色,被放入灰色队列中等候进一步处置惩罚。同时,B对象已完成了对其所有引用对象的标志,因此也将B对象标志为黑色…下一轮标志和上面是一样的逻辑,直至灰色队列为空。

然而,三色标志法存在一个潜伏的题目,即用户线程可能同时修改对象的引用关系,导致标志结果出现错误,如下图所示。

G1为了解决这个题目,使用了SATB算法(Snapshot At The Beginning, 初始快照)。
SATB算法的核心思想是在并发标志过程的起始阶段捕捉对象图的“逻辑快照”,并基于这个快照来举行后续的标志工作。在这个快照之后新生成的对象,会被直接标志为黑色,表示它们是活泼的,不应该被接纳(可能会产生浮动垃圾)。
为了处置惩罚在标志过程中可能发生的对象引用变化,SATB算法接纳了前置写屏障技能。这种技能会在引用赋值操纵(B.c = new C()或B.c = null)前触发,目的是捕获可能被删除的旧引用放入SATB待处置惩罚队列中。每个线程都有本身的SATB队列,但最终这些队列会被汇总到一个全局SATB队列中。
3.最终标志(Remark,STW)
配景的并发标志线程从全局SATB队列中取出对象,将其重新标志为灰色,并重新扫描其引用,将其子对象被递归标志为黑色。
4.扫除垃圾(Cleanup)
依赖并发标志周期产生的数据,举行Mixed GC,接纳所有Young区和部分Old区(对Old区的接纳价值和成本举行排序,根据用户所盼望的停顿时间来订定接纳计划。
参考资料

https://segmentfault.com/a/1190000044966735
https://www.cnblogs.com/meixiaoyu/articles/16672125.html
https://www.bilibili.com/opus/662248429116719137
https://developer.aliyun.com/article/1502777

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表