cms采用并发标记-清除算法,通过写屏障与卡表实现增量更新以避免漏标,重新标记仅扫描脏卡页、新生代及跨代引用对象,清除阶段允许浮动垃圾和内存碎片以换取零停顿。

标记-清除是核心,但不是简单两步
CMS 的“标记-清除”不是串行执行的朴素算法,而是被拆解为多个精细阶段,并嵌入并发控制逻辑。它不压缩内存、不移动对象,所以必须靠“标记”精准识别存活对象,再靠“清除”回收未标记空间。关键在于:**标记必须完整,否则会误删存活对象;清除必须安全,否则会干扰正在运行的应用线程。** 这就引出了并发处理机制——在用户线程不停止的前提下,保证标记结果最终一致。
并发标记如何避免漏标?靠写屏障 + 卡表
并发标记阶段,用户线程照常修改引用(比如把老年代对象 A 的字段指向新对象 B),而 GC 线程可能已扫过 A,导致 B 被漏标。CMS 用“增量更新(Incremental Update)”配合写屏障解决:
- 当用户线程修改老年代对象的引用字段时,JVM 通过写屏障拦截该操作
- 将该对象所在的内存页(称为“卡页”,Card)标记为“脏(Dirty)”并记录在卡表(Card Table)中
- 后续重新标记阶段只需扫描这些脏卡页,而非全堆,大幅缩短 STW 时间
这相当于把“全局一致性问题”降级为“局部修补问题”,是 CMS 实现低停顿的关键技术底座。
重新标记不是重头再来,而是精准补漏
重新标记(Remark)虽是 STW 阶段,但它的任务不是从头遍历整个堆,而是聚焦三类易变区域:
- 初始标记后晋升到老年代的新生代对象(需扫描整个新生代)
- 并发标记期间被用户线程修改过的老年代对象(即卡表中的脏卡)
- 年轻代中仍存活、且持有对老年代对象引用的对象(防止老年代对象因“跨代引用”丢失)
因此日志中常见 [Rescan (parallel)],说明该阶段可启用多线程并行扫描——它本质是一次带范围限定的“快速复查”,而非全量重标。
清除阶段允许浮动垃圾,也接受碎片化
并发清除阶段,GC 线程一边清理未标记对象,用户线程一边分配新对象。这意味着:
- 刚被清除的空间可能立刻被新对象占用,无需等待下一轮 GC
- 但清除不整理内存,空闲空间呈离散小块,形成碎片
- 部分在并发标记后才变成垃圾的对象(如标记后被断开引用),本次无法回收,称为“浮动垃圾”,留待下次处理
这种设计以空间碎片和少量浮动垃圾为代价,换来了清除阶段零停顿——正是 CMS “低延迟优先”哲学的直接体现。











