Java 中的 CMS 和 G1 垃圾收集器如何维持并发收集的正确性?
本文由 AI 生成,内容仅供参考,请仔细甄别。
在 Java 中,CMS 和 G1 垃圾收集器都支持“并发收集”以减少 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 与应用线程并发执行时的安全与正确。