Java 中的 CMS 和 G1 垃圾收集器如何维持并发的正确性?
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 之间区域的对象都是新分配的对象都认为其是存活的即可。
增量更新进一步理解
增量更新则是指在并发处理过程中,垃圾收集器可以分阶段、逐步更新对象的状态或引用信息,而不是在某个时刻一次性处理所有对象。
cms 会在 remark 阶段需要重新扫描所有线程栈和整个年轻代,重新扫描过就不会出现漏标。但如果年轻代的对象很多的话会比较耗时。
要注意这阶段是 STW 的,所以 CMS 也提供了一个 CMSScavengeBeforeRemark 参数,来强制 remark 阶段之前来一次 YGC,减少堆中的对象数量,减低扫描的耗时。
相比而言 g1 通过 SATB 的话在最终标记阶段只需要扫描 SATB 记录的旧引用即可,从这方面来说会比 cms 快,但是也因为这样浮动垃圾会比 cms 多。