1.G1 MixedGC 混合收集
老年代的堆空间使用不停增加,内存占用达到了参数-XX:InitiatingHeapOccupancyPercentt(默认45%)设定的值就会触发Mixed GC,当老年代大小占整个堆大小百分比达到该阈值时,回收所有的新生代和部分老年代,超过这个值就开始触发全局标记,进而触发MixedGC
- 根据用户设置的GC停顿时间来确定老年代垃圾收集的先后顺序。
- G1的垃圾收集 Mixed GC,主要使用复制算法,需要把各个Region中存活的对象复制到另一个空闲的Region
- 如果在复制过程中发现没有足够的空Region放复制的对象,那么就会触发一次Full GC。
G1 Mixed GC:
Mixed GC步骤主要分为两步:
- 全局并发标记(global concurrent marking)
- 拷贝存活对象(evacuation)
这里需要特别注意的是 Mixed GC 并不是 Full GC,只有当 Mixed GC 来不及回收old region 老年代的时候,也就说在需要分配老年代的对象时,但发现没有足够的空间,这个时候就会触发一次 Full GC
2.G1 MixedGC 全局标记阶段过程
2.1 全局并发标记(global concurrent marking)
-XX:InitiatingHeapOccupancyPercent: 默认值45,意思是老年区已使用空间/整个堆空间的比例, 如果超过阈值, 就会先触发全局标记, 进行 global concurrent marking 标记周期,当达到IHOP阈值时,G1并不会立即发起并发标记周期,而是等待下一次年轻代收集,利用年轻代收集的STW时间段,完成初始标记,这种方式称为借道(Piggybacking)。 在 G1 GC 中,它并不是一次GC过程的必须环节,通常是多次执行了YoungGC之后才会进行一次GlobalConcurrentMarking, 它主要是为 Mixed GC 提供标记服务的。 global concurrent marking的执行大致上可以划分为五个步骤:
- 初始标记(initial mark,STW)
整个过程STW,标记了从GC Root可达的对象,这些对象全都是 GC Roots 节点以及直接可达的对象,虽然会发生stop the world,但是该阶段耗时很短,很快就会结束, 不会占用太多的停顿时间
- 根区域扫描(root region scan)
初始标记暂停结束后,年轻代收集也完成的对象复制到Survivor存活区的工作,应用线程开始活跃起来,所有被就复制到survivor去与的对象,都需要被扫描标记为根对象,这个过程被称为根区域扫描
根区域扫描,就是标记存活区中(即 survivor 区)中指向老年代的被初始标记标记的引用的对象。它会标记所有的从所谓的根区可以到达的对象, 这个阶段与应用程序并发运行,并且只有完成该阶段后,才能开始下一次 STW 的 young GC。
- 并发标记(Concurrent Marking)
从名字上来看, 该过程就是和应用程序并发执行,线程数量由参数 -XX:ConcGCThreads (默认GC线程数的1/4,即-XX:ParallelGCThreads/4)控制, 从 GC Roots 对堆中的对象进行可达性分析,标记整个堆Heap的存活对象,每个线程只扫描一个分区,收集每个Region的存活对象信息,标记出来存活的对象图,同时并发标记线程还会定期检查并且同时处理STAB全局缓冲区列表的记录,更新对象引用信息
并发标记的过程可能被 young GC 中断,并发标记阶段产生的新的引用(或引用的更新)会被 SATB 记录下来, 所有的标记任务必须在堆满前就完成扫描,如果并发标记耗时很长,那么有可能在并发标记过程中,又会经历多次年轻代收集。如果堆满前没有完成标记任务,则会触发担保机制,经历一次长时间的串行Full GC
在并发标记过程中,会计算每个区域中对象的存活比例。如果在此阶段中,如果发现区域中的所有对象都是垃圾,那这个区域会立即被回收。
- 重新标记(Remark,STW)
重新标记也叫最终标记 final marking 阶段,为了修正在并发标记期间,因应用程序继续运作而导致标记产生变动的那一部分标记记录,G1需要一个暂停的时间,去处理剩下的SATB日志缓冲区和所有更新
G1 使用的是比 CMS 更快的初始快照算法 SATB 算法:snapshot-at-the-beginning。它只需要扫描SATB(Snapshot At The Beginning)的buffer,处理在并发阶段产生的新的存活对象,去处理剩下的 SATB日志缓冲区和所有更新,找出所有未被访问的存活对象
对比CMS 垃圾收集器,CMS的 remark重新标记阶段需要扫描整个mod union table的标记为dirty的entry以及全部根
- 清除(Cleanup, STW)
该阶段STW, 该阶段会计算每一个Region里面存活的对象,并把完全没有存活对象的Region直接放到空闲列表中。在该阶段还会重置Remember Set, 并且对每个Region进行排序
排序是为了找出各个 Region 的回收价值和成本,并根据用户所期望的GC停顿时间来制定回收计划。(这个阶段并不会实际去做垃圾的回收,也不会执行存活对象的拷贝)
清除阶段执行的详细操作如下:
- RSet梳理:启发式算法会根据活跃度和RSet尺寸对每一个Region分区定义不同等级,同时RSet梳理也有助于发现无用的引用。
- 整理堆分区:目的是区分混合收集识别回收收益高(基于释放空间和暂停目标)的老年代分区集合;
- 识别所有空闲分区:即发现没有任何对象存活对象的分区,该分区可在清除阶段直接回收,无需等待下次收集周期。
2.2 并发标记后
- 并发标记结束以后,老年代中100%为垃圾的 region 就直接被回收了,仅部分为垃圾的region会进行混合回收
- 根据停顿目标,G1 可能没法一次性回收掉所有的old region 候选分区,只能选择优先级高的若干个 region 进行回收
- 对优先级高的,回收垃圾性价比高的Region 被分成8次回收(可以通过 -XX:G1MixedGCCountTarget 设置,默认阈值8)
- 垃圾占内存分段比例越高的,越会被先回收。由参数-XX:G1MixedGCLiveThresholdPercent(默认65%)阈值决定内存分段是否被回收
- 如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间,计算出来回收性价比不高
- 混合回收并不一定要进行8次,由参数-XX:G1HeapWastePercent(默认值 10%)控制
- -XX:G1HeapWastePercent允许整个堆内存有 10% 的空间浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收,这样利用率比较高
- 否则 GC计算完,回收该区域会花费很多的时间,但是回收到的内存却很少,回收性价比不高
2.3 拷贝存活对象(evacuation)
对象复制过程又叫作Evacuation,是一个STW过程。
- 这一步会复用YongGC的逻辑,只是正常YGC的Choose Set只选择Young Region分区
- Mixed GC复用YongGC代码,在创建Choose Set时会选择所有Young Region和部分收益较高的Old Region
- 将CSet中的存活对象复制到Survivor Region存活区,然后回收原来的Region空间
- 如果发现没有足够的Region能够存放这部分拷贝的对象,就会触发FullGC
3. Full GC
FullGC采用的是 Serial Old GC, 单线程收集老年代对象
- 停止系统程序,单线程进行标记、清理和压缩整理
- Full GC 会对整堆做标记清除和压缩, 默认参数 XX:G1ReservePercent(默认10%)可以保留空间 用于垃圾回收
- 清理空间,腾出空闲的一批Region来供下一次Mixed GC使用
- 该过程是非常耗时的,而且Full GC的收集代价非常高, 能够明显感觉到应用程序的停顿
- 应该尽量避免Full GC的发生。
G1在以下场景中会触发 Full GC,同时会在日志中记录to-space-exhausted以及Evacuation Failure:
- 从年轻代分区拷贝存活对象时,无法找到可用的空闲分区
- 从老年代分区转移存活对象时,无法找到可用的空闲分区
- 分配巨型对象时在老年代无法找到足够的连续分区,也会提前触发FullGC
至此, 我们讲解了G1 垃圾收集器的Mixed GC的 垃圾回收过程, 明天我们分析 下G1的 垃圾回收日志