CMS采用增量更新写屏障捕获并发标记期间的引用修改,将被修改字段的对象加入脏卡队列,并在STW的重新标记阶段扫描这些区域,确保不漏标;但不处理新对象分配引发的漏标问题。

CMS(Concurrent Mark-Sweep)垃圾回收器在并发标记阶段(Concurrent Marking Phase)是与用户线程并发执行的,因此对象引用关系可能在标记过程中被修改。为保证标记结果的准确性,CMS 采用增量更新(Incremental Update)机制来捕获并处理这些变动。
为什么需要处理引用变动
并发标记期间,应用线程仍在运行,可能执行如下操作:
- 将某个对象的字段从 null 改为指向新对象(如
obj.field = newObj) - 修改已有引用,使其指向另一个对象(如
obj.field = otherObj) - 将字段从指向 A 对象改为 null 或指向 B 对象
若不记录这些写操作,可能导致原本可达的对象被误判为不可达,从而被错误回收(漏标问题)。
CMS 使用写屏障捕获引用更新
CMS 在并发标记开始前,会开启一种称为“增量更新写屏障(Incremental Update Write Barrier)”的机制。每当用户线程执行类似 obj.field = newRef 的引用赋值时,JVM 会在该写操作前后插入一段额外逻辑:
- 检查当前是否处于并发标记阶段
- 若满足条件,将被修改的字段所在的对象(即
obj)加入一个名为“卡表(Card Table)”或更准确地说,“已修改卡(dirty card)”队列中 - 后续在重新标记阶段(Remark),GC 线程会扫描这些卡对应的内存区域,重新标记其中可能遗漏的存活对象
重新标记阶段(Remark)负责兜底
并发标记无法完全覆盖所有变动,因此 CMS 设计了 STW 的重新标记阶段:
- 暂停所有用户线程
- 扫描 GC Roots 直接引用的对象(如栈、寄存器、JNI 引用等)
- 扫描写屏障记录的“脏卡”,对其中对象的引用字段做二次标记
- 确保所有在并发标记期间被新创建或新建立的引用链都被覆盖
这个阶段是 CMS 停顿时间较长的主要原因之一,尤其在老年代对象频繁更新时,脏卡数量多,扫描开销大。
注意:CMS 不处理“新对象分配”引发的漏标
CMS 的增量更新机制主要应对已有对象字段被修改的情况;而新对象的分配(如在 Eden 区或老年代直接分配)本身不会导致漏标——因为新对象一定由 GC Roots 可达(至少被栈或寄存器临时引用),且 CMS 在 Remark 阶段会重新扫描 GC Roots。
但需注意:如果新对象仅被刚分配它的局部变量引用,而该变量很快出作用域,又未被其他长期存活对象引用,则它仍可能在下次 GC 中被回收,这属于正常语义,不是漏标。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











