hotspot_jvm_GC_CMS

CMS

CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器.

JVM 中,老年代的gc可以使用CMS.

触发点

1 周期性触发

2 条件性触发

  • 老年代使用率达到阈值
  • 新生代的晋升担保失败

主要是上面两个,还有其他情况暂不考虑.

收集过程

CMS是一种优化的mark-sweep算法,mark-sweep的核心是对象树的遍历, 面临的问题是这个数非常的巨大.但还是基于简单的图的遍历算法:三色标记法.

对象在标记过程中,根据标记情况,分成三类:

  1. 白色对象,表示自身未被标记;
  2. 灰色对象,表示自身被标记,但内部引用未被处理;
  3. 黑色对象,表示自身被标记,内部引用都被处理;
Phase 1: InitialMarking(初始化标记,整个过程STW)

该阶段单线程执行,主要分分为两步:

  1. 标记GC Roots可达的老年代对象;
  2. 遍历新生代对象,标记可达的老年代对象;

标记结束后,如下图:

img

Phase 2: Marking(并发标记)

该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。

因为该阶段并发执行的,在运行期间可能发生

  • 新生代的对象晋升到老年代、
  • 或者是直接在老年代分配对象、
  • 或者更新老年代对象的引用关系等等.

对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。

为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。

img

Phase 3: Concurrent Preclean 预清理

This is again a concurrent phase, running in parallel with the application threads, not stopping them. While the previous phase was running concurrently with the application, some references were changed. Whenever that happens, the JVM marks the area of the heap (called “Card”) that contains the mutated object as “dirty” (this is known as Card Marking).

总的来说, 这个阶段是一个并行标记过程.

主要目的:减轻Final remark的执行时间.

主要做两件事情:

  1. 处理新生代已经发现的引用,比如在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B之前没有被标记),在这个阶段就会标记对象B为活跃对象。
  2. 在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty(其实这里并非使用CardTable,而是一个类似的数据结构,叫ModUnionTalble),通过扫描这些Table,重新标记那些在并发标记阶段引用被更新的对象(晋升到老年代的对象、原本就在老年代的对象). Dirty card 会被清理干净,当这个card中的对象的可达对象都被标记之后.如下面三个图:

CMS concurrent marking

CMS dirty cards

CMS concurrent preclean

#####

Phase 4: Concurrent Abortable Preclean 可中断的预清理

又是一个并行标记过程.

主要目的: 减轻Final Remark 的执行时间.

该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。

在该阶段,主要循环的做两件事:

  1. 处理 From 和 To 区的对象,标记可达的老年代对象
  2. 和上一个阶段一样,扫描处理Dirty Card中的对象

当然了,这个逻辑不会一直循环下去,打断这个循环的条件有三个:

  1. 可以设置最多循环的次数 CMSMaxAbortablePrecleanLoops,默认是0,意思没有循环次数的限制。
  2. 如果执行这个逻辑的时间达到了阈值CMSMaxAbortablePrecleanTime,默认是5s,会退出循环。
  3. 如果新生代Eden区的内存使用率达到了阈值CMSScheduleRemarkEdenPenetration,默认50%,会退出循环。(这个条件能够成立的前提是,在进行Precleaning时,Eden区的使用率小于十分之一)

如果在循环退出之前,发生了一次YGC,对于后面的Remark阶段来说,大大减轻了扫描年轻代的负担,但是发生YGC并非人为控制,所以只能祈祷这5s内可以来一次YGC。

Phase 5: Final Remark (STW)

这是第二个会STW的步骤,也是最后一个.这个步骤的目的是标记Old Generation中的所有存活对象. 因为之前的标记步骤都是和用户线程并发进行的,所以需要一个STW来确保所有的存活对象不会被误回收.

进行如下的处理:

  1. 遍历新生代对象,重新标记
  2. 根据GC Roots,重新标记
  3. 遍历老年代的Dirty Card,重新标记,这里的Dirty Card大部分已经在clean阶段处理过

之所以要在之前进行Phase 3,4,主要原因是为了减少标记的时间,虽然在phase5中还是要从ROOT扫描全部,但是已经标记过的对象就会快速跳过.

Phase 6: Concurrent Sweep

清理垃圾对象,这个阶段GC线程和用户线程并发执行。

Phase 7: Concurrent Reset

重置CMS收集器的数据结构,做好下一次执行GC任务的准备工作。

参考