Java 中的 CMS 和 G1 垃圾收集器如何维持并发的正确性?

Sherwin.Wei Lv8

Java 中的 CMS 和 G1 垃圾收集器如何维持并发的正确性?

回答重点

CMS 和 G1 分别通过增量更新(Incremental update)和 SATB(snapshot-at-the-beginning)来打破这两个充分必要条件,维持了 GC 线程与应用线程并发的正确性。

并发执行可能出现对象漏标,而漏标的两个充分必要条件是:

1)将新对象插入已扫描完毕的对象中,即插入黑色对象到白色对象的引用。

2)删除了灰色对象到白色对象的引用。

CMS 用了增量更新,打破了第一个条件,通过写屏障将插入的白色对象标记成灰色,即加入到标记栈中,在 remark 阶段再扫描,防止漏标情况。

G1 用了 SATB,打破了第二个条件,会通过写屏障把旧的引用关系记下来,之后再把旧引用关系扫描一遍。

扩展知识

SATB 进一步理解

SATB(Snapshot At The Beginning) 是一种用于垃圾回收的技术,主要目的是在并发垃圾回收过程中确保对象的引用关系是准确的。SATB 机制在开始标记时会记录一个快照,快照的时候对象是存活的,后续就一直认为它是存活的(当然 gc 过程中新分配的对象也都认为是活的),之后这些对象的任何引用变化都会被跟踪,这样在标记阶段不会遗漏任何可达对象。

快照后,如何知道哪些对象是 gc 过程中新分配的对象?

G1 中每个 region 会维持 TAMS (top at mark start)指针,分别是 prevTAMS 和 nextTAMS 分别标记两次并发标记开始时候 Top 指针的位置。

Top 指针就是 region 中最新分配对象的位置,所以 nextTAMS 和 Top 之间区域的对象都是新分配的对象都认为其是存活的即可。

68747470733a2f2f63646e2e6a7364656c6976722e6e65742f67682f79657373696d6964612f63646e5f696d6167652f696d672f32303232303132333136333733372e706e67.png

增量更新进一步理解

增量更新则是指在并发处理过程中,垃圾收集器可以分阶段、逐步更新对象的状态或引用信息,而不是在某个时刻一次性处理所有对象。

cms 会在 remark 阶段需要重新扫描所有线程栈和整个年轻代,重新扫描过就不会出现漏标。但如果年轻代的对象很多的话会比较耗时。

要注意这阶段是 STW 的,所以 CMS 也提供了一个 CMSScavengeBeforeRemark 参数,来强制 remark 阶段之前来一次 YGC,减少堆中的对象数量,减低扫描的耗时。

相比而言 g1 通过 SATB 的话在最终标记阶段只需要扫描 SATB 记录的旧引用即可,从这方面来说会比 cms 快,但是也因为这样浮动垃圾会比 cms 多。

Comments