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

本文由 AI 生成,内容仅供参考,请仔细甄别。
在 Java 中,CMSG1 垃圾收集器都支持“并发收集”以减少 Stop-The-World(STW)暂停时间。它们在并发标记和处理过程中,通过 写屏障(Write Barrier)机制引用变更记录策略 来保证 GC 与应用线程并发运行时的正确性

下面从两者的原理角度详细分析其并发正确性保证机制:


一、CMS 如何维持并发正确性

🔹 CMS 主要并发阶段:并发标记阶段

  • CMS 在“老年代”使用 标记-清除算法
  • 应用线程在并发标记过程中会继续修改对象引用;
  • 为了不漏标活对象,CMS 使用了 增量更新(Incremental Update)策略

✅ 增量更新(Incremental Update)的含义:

当应用线程在 GC 并发标记期间 创建新的引用(A → B),GC 可能尚未扫描到 A;
因此,必须记录这类“新引用”并补充标记目标对象 B。

📌 写屏障实现逻辑:

// 当 A.f = B 时(建立新引用)
if (GC正在并发标记 && B未被标记) {
    把 B 加入 GC 的标记队列
}

✅ 小结:

  • CMS 捕捉 新建立的引用
  • 使用写屏障记录这些变更,防止 漏标对象
  • 这样就能在并发标记过程中保持可达性分析的完整性。

二、G1 如何维持并发正确性

🔹 G1 的并发标记机制也依赖写屏障,但策略不同 —— 使用 SATB(Snapshot-At-The-Beginning)

G1 在并发标记开始时“拍下快照”;
不管之后引用怎么变,都基于那一刻的引用图进行标记。

✅ SATB 的处理逻辑:

当某个引用被断开(A 不再指向 B),但在快照那一刻 A 是指向 B 的,那么 B 应该仍被认为是“活的”。

📌 写屏障记录旧值:

// 当 A.f = null(断开旧引用 B)时
if (GC正在并发标记 && B未被标记) {
    把 B 加入 SATB Buffer(辅助标记队列)
}

✅ 小结:

  • G1 捕捉 断开引用的旧对象
  • 用写屏障记录快照时的状态;
  • 避免因引用断开导致“误清活对象”。

三、对比总结

特性 CMS(Incremental Update) G1(SATB)
标记策略 捕捉“新增引用” 捕捉“断开引用前的对象”
写屏障作用 记录新指向的对象 记录断开的旧对象
风险防范 防止漏标新连接对象 防止误清已存在但引用断开的对象
视图一致性策略 最终引用图一致性 快照视图一致性
适用阶段 并发标记 并发标记
成本对比 有一定开销 相对较高但更准确

四、整体保证正确性的核心机制

两种 GC 并发正确性的维持手段主要包括:

机制 说明
写屏障 拦截并记录引用变更行为
Remembered Set 记录跨代引用变化,确保分代 GC 的正确性
并发标记队列 存放引用变更对象,供 GC 补充标记
起始快照/增量策略 不同 GC 对引用视图一致性采用不同策略(SATB vs Incremental Update)

五、举例说明

例子:对象 A → B,在并发标记期间:

操作 CMS 的做法 G1 的做法
应用线程新建引用 A → C 写屏障记录 C,标记 C 忽略
应用线程断开 A → B 忽略 写屏障记录 B,补标记 B

六、总结一句话

CMS 和 G1 都使用写屏障机制维持并发标记时的可达性图正确性。CMS 采用增量更新策略,记录新建立的引用防止漏标;G1 采用 SATB 策略,记录引用断开前的旧对象,保证不误清。两者都通过对引用变更的监控,确保 GC 与应用线程并发执行时的安全与正确。


posted @ 2025-06-04 21:49  MuXinu  阅读(38)  评论(0)    收藏  举报