CMS 收集器原理

候选人小冯在面试美团 P6 时,面试官问道:

"CMS 收集器的执行流程是什么?"

小冯说:"有初始标记、并发标记、重新标记..."面试官追问:"CMS 在哪个阶段会 Stop-The-World?"

小冯答不上来。面试官继续追问:"CMS 有什么缺点?"

小冯彻底卡住了...

一、核心问题:CMS 收集器原理 🔴

1.1 问题拆解

第一层:四个阶段(有哪些?)
  "CMS 的四个执行阶段是什么?"
  考察点:初始标记/并发标记/重新标记/并发清除

第二层:STW 阶段(什么时候停?)
  "CMS 在哪些阶段会 Stop-The-World?"
  考察点:初始标记、重新标记

第三层:缺点与废弃(有什么问题?)
  "CMS 被废弃的原因是什么?"
  考察点:内存碎片、浮动垃圾、并发失败

1.2 ❌ 错误示范

候选人原话 A:"CMS 是并发收集,整个过程都不停顿。"

问题诊断:CMS 的并发标记并发清除阶段不停顿,但初始标记重新标记阶段仍然需要 Stop-The-World。

候选人原话 B:"CMS 没有内存碎片问题。"

问题诊断:CMS 使用标记-清除算法,会产生内存碎片。长期运行后可能触发 Serial Old 的 Full GC(最长的停顿)。

1.3 标准回答

P5 级别:四个阶段

CMS 的四个阶段

graph TD
    A[初始标记 STW<br/>标记 GC Roots 直接引用的对象] --> B[并发标记<br/>用户线程运行]
    B --> C[重新标记 STW<br/>修正并发标记期间的变动]
    C --> D[并发清除<br/>用户线程运行]
    D --> A

    style A fill:#ff6b6b
    style C fill:#ff6b6b
阶段是否 STW耗时工作内容
初始标记✅ STW标记 GC Roots 直接引用的对象
并发标记❌ 并发追踪引用链,标记所有可达对象
重新标记✅ STW较长修正并发标记期间产生的浮动垃圾
并发清除❌ 并发清除未标记的对象

P6 级别:CMS 的问题

CMS 的三大问题

1. 内存碎片:标记-清除不整理,产生大量内存碎片。导致的问题:

  • 分配大对象时无法找到连续空间
  • 触发 Full GC(Serial Old) 进行内存整理,停顿时间可能长达数秒

2. 浮动垃圾(Floating Garbage)

graph TD
    A[并发标记期间] --> B[对象 A 被标记为可达]
    B --> C[并发清除前]
    C --> D[对象 A 变成不可达<br/>但已标记)
    D --> E[对象 A 成为浮动垃圾<br/>下一次 GC 才能回收]

并发标记期间变成不可达的对象被称为"浮动垃圾",必须在下一次 GC 时才能清理。

3. 并发失败(Concurrent Mode Failure)

如果并发清除期间老年代满了(无法容纳浮动垃圾 + 新分配的对象),CMS 启动 Full GC(Serial Old)

# 日志特征
[Full GC [CMS: CMS-initial-mark |
  concurrent-mode-failure |
  allocation-failure

降低 Concurrent Mode Failure 风险

-XX:CMSInitiatingOccupancyFraction=70  # 老年代使用率 70% 时开始回收
-XX:+UseCMSInitiatingOccupancyOnly       # 只使用上述阈值
-XX:CMSFullGCsBeforeCompaction=5         # 5 次 Full GC 后进行一次内存整理

P7 级别:JDK 14 废弃的工程原因

CMS 被废弃的原因

CMS 的代码复杂度极高(10 万行级别),维护成本大:

  1. 并发阶段与用户线程共享 CPU:CMS 的并发阶段消耗 CPU 资源,降低应用吞吐量

  2. 浮动垃圾问题无解:无法从根本上解决,只能推迟

  3. 碎片化问题:标记-清除算法无法解决碎片

JDK 的替代方案

  • JDK 9:G1 成为默认收集器
  • JDK 11+:ZGC、Shenandoah 成为正式特性
  • JDK 14:CMS 被标记为 deprecated

【面试官心理】 这道题我能问到 P7 级别,是因为 CMS 的废弃涉及了工程维护成本和 GC 技术演进的权衡。能说清 CMS 被废弃的工程原因的候选人说明他有技术视野。

1.4 追问升级

追问:CMS 的重新标记需要扫描什么?

重新标记需要扫描:

  1. 在并发标记期间新晋升到老年代的对象(之前在年轻代未被标记)
  2. 在并发标记期间新分配的对象(分配到老年代的对象)
  3. 在并发标记期间引用关系发生变化的对象

二、生产调优 🟡

2.1 CMS 参数配置

# 启动 CMS
-XX:+UseConcMarkSweepGC

# 关键参数
-XX:CMSInitiatingOccupancyFraction=70  # 老年代使用率 70% 开始回收
-XX:+UseCMSCompactAtFullCollection    # Full GC 时整理内存
-XX:CMSFullGCsBeforeCompaction=5     # 5 次 Full GC 后整理

2.2 常见问题

# Concurrent Mode Failure
# 原因:CMS 并发清除期间老年代满了
# 解决:调低 CMSInitiatingOccupancyFraction

# Promotion Failed
# 原因:Survivor 空间不足,对象晋升老年代失败
# 解决:增大 Survivor 区和年轻代
💡

面试加分点:能说出"CMS 的并发模式失败和 G1 的 Failure 模式非常相似,G1 借鉴了 CMS 的教训,在并发标记阶段更保守",说明他理解了 GC 技术的演进关系。

⚠️

面试陷阱:被问到"CMS 的并发标记阶段,用户线程也在运行,会漏标记对象吗",很多人会说"会漏标记"。准确答案是:不会漏标记,因为并发标记阶段会使用三色标记算法,并且重新标记阶段会修正并发期间的引用变化(通过 Card Table)。