CMS 的底层原理是什么?优势在哪?

#Java#面试八股

CMS 的底层原理是什么?优势在哪?

结论先行:CMS(Concurrent Mark Sweep)是第一款真正意义上的并发老年代收集器,把「标记」和「清除」都拆成可与用户线程并行的阶段,只有初始标记和重新标记两次短暂 STW。它用**增量更新(incremental update)**配合写后屏障解决并发标记的漏标问题,代价是不压缩内存、产生浮动垃圾,最终被 G1 取代。

一、四个阶段

阶段是否 STW做什么耗时
初始标记是标记 GC Roots 直接可达的对象极短,与根数量相关
并发标记否从根出发遍历对象图,同时处理写屏障记录最长,与应用并行
重新标记是处理增量更新队列,修正漏标对象短,但受脏卡数量影响
并发清除否清除不可达对象,不移动存活对象较长,与应用并行
时间轴(一个 CMS 周期):
应用线程 ──████████████████████████████████████──
CMS 线程      ██        ███████████████        ██████████
             初始标记   并发标记          重新标记  并发清除
              (STW)                        (STW)

二、增量更新如何防漏标

并发标记时应用线程在改引用,可能出现「黑色对象新增了指向白色对象的引用」而标记线程已扫过该黑对象,导致白对象被误清。CMS 的写后屏障在引用赋值完成后把被赋值的引用记录到队列:

// 概念示意:CMS 的写后屏障(实际由 JIT 在字节码层面插入)
void oop_field_store(oop* field, oop new_value) {
    *field = new_value;                       // 先真正赋值
    if (is_marking() && new_value != null && !is_black(new_value)) {
        enqueue_for_revisit(field);           // 再把新增引用入队,重新标记阶段重扫
    }
}

重新标记阶段会以 GC Roots 与这些队列为起点重扫一遍,因此 CMS 的 remark 停顿与「并发期间修改的引用数量」成正比。这也是 CMSScavengeBeforeRemark 存在的原因:先做一次 Young GC,减少新生代对象晋升带来的脏卡扫描量。

三、优势与代价

优势代价
停顿短,适合响应敏感的服务不压缩,产生碎片,大对象分配易失败
并发清除不移动对象,无需更新引用浮动垃圾只能下轮回收,需预留空间
实现成熟,JDK 8 上大量生产验证并发模式失败后退化为单线程 Full GC,停顿从毫秒变秒级
可与 ParNew 组合覆盖新生代并发线程抢占 CPU,吞吐下降约 10%~20%

四、碎片与失败怎么治理

# 提前启动并发收集,给浮动垃圾留余量
java -XX:+UseConcMarkSweepGC \
     -XX:CMSInitiatingOccupancyFraction=70 \
     -XX:+UseCMSInitiatingOccupancyOnly \
     -XX:+CMSParallelRemarkEnabled \
     -XX:+CMSScavengeBeforeRemark \
     -XX:CMSScheduleRemarkEdenPenetration=50 \
     -jar app.jar

# 碎片治理:每 5 次 Full GC 做一次压缩整理
java -XX:+UseConcMarkSweepGC \
     -XX:CMSFullGCsBeforeCompaction=5 \
     -XX:+UseCMSCompactAtFullCollection \
     -XX:+PrintGCDetails -jar app.jar

# 观察碎片与失败:搜索日志中的关键字
grep -E "concurrent mode failure|promotion failed|ParNew" gc.log | tail -20

关键指标是「老年代使用率」与「并发模式失败次数」。一旦日志频繁出现 concurrent mode failure,说明收集器已经追不上分配速率,光调参数救不了,必须换 G1 或优化对象分配。

五、常见追问

追问回答要点
CMS 用什么算法标记-清除,所以不移动对象、不压缩
为什么 CMS 不压缩压缩需要移动对象并更新所有引用,必须 STW,与「低停顿」目标冲突
重新标记为什么可能很长要扫描脏卡与增量更新队列;新生代对象多、引用修改多时明显变慢
CMS 与 G1 的标记差异CMS 用增量更新(写后屏障记新增),G1 用 SATB(写前屏障记删除)
CMS 现在还能用吗JDK 9 标记废弃、JDK 14 移除;只在老版本 JDK 8 环境里还能见到

小结

CMS 的价值在于证明了「大部分标记工作可以并发做」,优势是停顿短,代价是碎片与浮动垃圾。答这一题要能讲出「四个阶段 + 增量更新 + 两个代价」,就足够深入了。

笔记加载中…