开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 33 天,点击查看活动详情
1.G1工作原理
G1把堆分为不同的区域,叫做Region,每次只处理一部分Region,通过计算每个Region回收所需时间、可回收空间的性价比来决定哪块区域Region被回收
在介绍工作原理前,我们先介绍几个基本概念,Region分区,RSet记忆集合,CardTable 卡表,这三个概念必须知道,他们三个的关系如图所示
1.1 Region分区
G1 垃圾收集器将堆内存划分为若干个 Region,每个 Region 分区只能是一种角色
- Eden区
- Survivor区
- To区
- 老年代
- Humongous大对象区域
- 未分配区域
Humongous区域专门用于存放巨型对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个巨型对象。之前的垃圾收集器,这些巨型对象默认会被分配在老年代,但如果它是一个短期存活的巨型对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC。
为了解决这个问题,G1划分了一个H区专门存放巨型对象,如果一个H区装不下巨型对象,那么G1会寻找连续的H分区来存储,如果寻找不到连续的H区的话,就不得不启动 Full GC 区处理回收垃圾,用来存放该超大对象。
1.2 RSet记忆集合
Rset(Remembered Set)被称为记忆集合,他会在每个Region初始化时,初始化一个Remembered Set(记忆集合)简称RSet,记录的就是其他Region区间对 本Region对象的引用,说明白点就是 谁用了我的分区中的对象,避免整堆扫描,只需要扫描Rset即可,方便快捷的找到对象引用信息,加快垃圾回收速度
为什么有Rset???
- 之前的收集器,面对跨代收集的问题时,都无法很好的解决,比如年轻代引用了老年代对象或者老年代对象引用年轻代对象
- Young GC在回收年轻代时,需要判断年轻代的对象是否存活,而年轻代的部分对象可能被老年代的对象引用,因此必须扫描老年代才不会误判
- Full GC 在回收老年代时,也需要老年代的对象是否被年轻代对象引用,因此必须扫描年轻代,才不会误判
- 这种扫描效率低下,相当于整堆扫描,时间久,效率低,难以满足要求
所以就产生了Rset(Remembered Set)记忆集合用来记录跨代,跨区的对象的引用关系,每次只需要扫描Rset就可以知道对象的引用关系,避免整堆扫描
1.3 CardTable 卡表
CardTable 称为卡表,卡表的概念最早是在CMS 垃圾收集器引入的,他其实是Rset的反方向,记录的是我用了谁
CMS 在进行 YGC 时,如何判断是否存在新生代引用了老年代对象?简单做法就是扫描整个老年代,但是这个代价太大了,因此CMS垃圾收集器 引入了卡表来解决这个问题。
卡表又称为卡片标记(card marking)
- 逻辑上将老年代空间分割为若干个固定大小的连续区域,分割出来的每一个个区域就称为卡片(card)
- 每个卡片都有一个与其对应的标记位,最简单的实现方案是由字节数组实现,以卡的编号作为索引
- 每个卡的大小通常介于128~512字节之间,一般使用2的幂字节大小
- 卡片内部发生引用变化时(指针写操作),写屏障(Write Barrier)会将该卡在卡表中对应的字节标记为脏(dirty)
- 在 YGC 时,只需将卡表中被标记为 dirty 的 card 也作为扫描范围,从而避免扫描整个老年代堆
- 使用卡表(Card Table)和写屏障(Write Barrier)来进行标记,加快对GC Roots的扫描。
G1的卡表
G1的Rset记忆集在存储结构的本质上是一种哈希表
- Key是别的Region的起始地址,Value是一个集合
- Value里面存储的元素是卡表的索引号。这是一种"双向"的卡表结构 (卡表是"我引用了谁",这种结构还记录了"谁引用了我")比原来的卡表实现起来更加复杂
- 由于Region数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担
- G1至少要耗费大约相当于Java堆容量10%至20%的额外内存来维持收集器工
2.G1 垃圾回收
G1的垃圾回收,逻辑上G1分为年轻代和老年代,但是它的比例不是固定的,只是逻辑上的区分,为了满足MaxGCPauseMillis 最大停顿时间限制,G1会自动调整两者之间的比例。这也是G1 JVM调优,不建议使用 -Xmn或者-XX:NewRatio去设定它们的比例的原因,由 G1自行调节,如果限制了年轻代的大小,那么设定的这个参数-MaxGCPauseMilli有可能会失效。
G1的回收过程主要分为2类
-
年轻代垃圾回收 Young GC
- G1年轻代的垃圾回收,同样叫Young GC ,该过程和其他的垃圾收集器大致相同,都是采用标记-复制策略
- YongGC 发生时机就是Eden区满的时候(Eden区大小参数受到-XX:G1MaxNewSizePercent参数影响
- G1根据-XX:MaxGCPauseMills参数调整GC时机。根据Region回收所需时间、可回收空间的性价比,保证垃圾回收的停顿时间不能超过预设时间,选取部分Region来进行垃圾回收。
- Young GC触发的时候会伴随着全局并发标记,他主要是为下一步Mixd GC提供标记服务的
-
混合收集,也叫Mixed GC(Mixed GC 不是FULL GC)
- Mixed GC不只清理年轻代,还会将老年代的一部分区域进行清理。
- G1参数-XX:InitiatingHeapOccupancyPercent,他的默认值是45%意思就是说,如果老年代占据了堆内存的45%的Region的时候,就尝试触发一个新生代+老年代一起回收的混合回收
- 如果Mixed GC 实在无法跟上主程序分配内存的速度,导致老年代填满无法继续执行Mixed GC
- 下面就会使用serial Old GC(FULL GC)来收集整个堆,本质上G1不提供FULL GC
3.G1 YoungGC 年轻代垃圾回收过程
JVM启动时,G1先准备好Eden区,程序在运行过程中不断创建对象到Eden区
- 当Eden区空间耗尽时,G1会启动一次年轻代垃圾回收过程,回收所有年轻代区域Eden区及Survivor区
- G1停止应用程序的执行(STW)
- 该过程并行的、独占式的(STW发生)的垃圾回收
- 可能会发生对象的代晋升,将会把对象放入Survivor或者是老年代
- 年轻代回收过程的回收集包含Eden区和Survivor区所有的内存分段
下面我们看下回收过程
- 第一阶段,选择收集集合 Choose Cset 选择收集集合(Choose CSet),G1会在遵循用户设置的GC暂停时间上限的基础上,选择一个最大年轻带区域数,将这个数量的所有年轻代区域作为收集集合
- 第二阶段,根处理 根是指static静态变量指向的对象、正在执行的方法调用链上的局部变量等。根引用连同记忆集Rset记录的外部引用,作为扫描存活对象的入口
- 第三阶段,更新RSet记忆集合
处理 dirty card 队列中的 card,更新 RSet记忆集合,此阶段完成后,RSet记忆集合可以准确的记录年轻代对象对老年代的引用,标记出老年代对本Region分区中的对象的引用 - 第四阶段:RSet扫描 Scan RS
根据Rset的记录,识别被老年代对象指向的 Eden 中的对象,这些有引用,指向的Eden中年轻代对象的,都被认为是存活的对象 - 第五阶段:对象拷贝
将上面计算标记出来的Eden 区存活的对象将被复制拷贝到 survivor to 区,如果幸存者区内存活的对象的年龄没有达到阈值,年龄会+1,如果年龄达到阈值会被复制到老年代未使用的空间中进行分配。 如果幸存者空间不够,Eden区的部分数据会直接晋升到老年代中 - 第六阶段:处理引用
处理软引用、弱引用、虚引用,最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的、没有碎片,因此复制过程可以达到内存整理的效果,减少碎片。
年轻代处理完毕后, 会伴随着老年代的全局并发标记
4.老年代并发标记过程
年轻代处理完毕后, 会伴随着老年代的全局并发标记,主要是为下一步Mixd GC提供标记服务的,并发标记过程类似于CMS的并发标记
分为以下几个阶段
- 初始化阶段 initial mark 标记从根Root节点直接可达对象,这个阶段是STW的,并且会触发一次年轻代GC.
- 根区域扫描 Root Region Scanning
G1 扫描幸存者Survivor区直接可达的老年代区域对象,并标记被引用的对象 - 并发标记 Concurrent Marking
在整个堆中进行并发标记(和应用程序并发执行),在并发标记阶段如果发现区域对象中的所有对象都是垃圾,那这个区域会立即回收。同时,并发标记过程中,大概计算每个区域的对象存活的比例和计算回收垃圾的时间。 - 最终标记 Remark
由于应用程序进行,需要修正上一次的标记结果,该阶段STW的。因为并发标记程序还在运行,对于引用对象变化造成的浮动垃圾,比如初始标记的时候被标记为垃圾的对象,现在不是垃圾对象了;也可能最开始的时候不是垃圾,但是现在变成垃圾了所以需要再次标记。G1中采用了比CMS更快的初始快照算法:snapshot一at一the一beginning (SATB) - 独占清理 Cleanup
计算各个区域的存活对象和GC回收比例。识别可以混合回收的区域,进行排序,该阶段是STW的,这个阶段并不会真实回收垃圾,是为了下阶段MixedGC做准备的 - 并发清理阶段
识别并清理完全空闲的区域