CMS 垃圾收集器的工作原理
CMS(Concurrent Mark Sweep) 是一款以获取最短回收停顿时间(Low Latency)为目标的垃圾收集器。它在 JDK 1.5 时代引入,是 Java 历史上第一款真正意义上的并发垃圾收集器,广泛应用于对响应时间要求较高的 B/S 系统服务端上。
CMS 作用于老年代,基于“标记-清除”(Mark-Sweep)算法实现。
下面为您详细拆解 CMS 垃圾收集器的工作原理、核心阶段以及它的优缺点。
一、 CMS 的四大核心阶段
CMS 的工作过程相对复杂,为了实现“并发”,它的核心流程被拆分成了 4 个主要阶段(如果细分会有 6-7 个,但核心是这 4 个)。
1. 初始标记(Initial Mark)—— STW (Stop-The-World)
- 动作: 仅仅只是标记一下 GC Roots 能直接关联到的对象,以及年轻代中存活对象引用的老年代对象。
- 特点: 速度非常快,因为不需要向下追溯整个对象图。
- 状态: 需要暂停所有用户线程(STW),但停顿时间极短。
2. 并发标记(Concurrent Mark)—— 并发执行
- 动作: 从“初始标记”阶段找到的 Roots 开始,遍历整个老年代的对象图,标记所有存活的对象。
- 特点: 耗时最长的一个阶段。
- 状态: 与用户线程并发执行,不需要暂停用户线程。
- 问题: 因为用户线程还在跑,这期间可能会产生新的垃圾,或者改变了对象的引用关系(原本活的变死了,原本死的被重新引用了)。
3. 重新标记(Remark / Final Remark)—— STW (Stop-The-World)
- 动作: 修正“并发标记”期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(主要采用增量更新 Incremental Update 算法来解决漏标问题)。
- 特点: 停顿时间比“初始标记”稍长,但远比“并发标记”短。
- 状态: 需要暂停所有用户线程(STW)。
4. 并发清除(Concurrent Sweep)—— 并发执行
- 动作: 清除掉未被标记的死亡对象,释放内存空间。
- 特点: 因为不需要移动存活对象(标记-清除算法),所以这个阶段也可以和用户线程并发。
- 状态: 与用户线程并发执行。
*(注:在并发标记和重新标记之间,实际上还有一个**并发预清理(Concurrent Preclean)*阶段,用于提前处理并发标记期间发生引用变化的卡表(Dirty Card),目的是尽量缩短“重新标记”阶段的 STW 时间。)
二、 CMS 的三大致命缺点(面试常考)
虽然 CMS 实现了低延迟,但它并不完美,由于其算法和并发特性的局限,存在三个著名的痛点:
1. 对 CPU 资源非常敏感
- 原因: 在并发标记和并发清除阶段,虽然不会导致 STW,但 GC 线程会占用一部分 CPU 资源,导致应用程序变慢,总吞吐量降低。
- 细节: CMS 默认启动的回收线程数是
(CPU 核心数 + 3) / 4。如果 CPU 核心数少于 4 个,CMS 对用户程序的影响就会非常大。
2. 无法处理“浮动垃圾”(Floating Garbage)
- 原因: 在“并发清除”阶段,由于用户线程还在运行,这期间必然会产生新的垃圾。这部分垃圾出现在标记过程之后,CMS 无法在本次收集中处理它们,只能留到下一次 GC 时清理,这些垃圾被称为“浮动垃圾”。
- 后果(Concurrent Mode Failure): 因为有浮动垃圾的存在,以及并发运行期间用户线程需要内存,CMS 不能等老年代几乎满了再收集,必须预留一部分空间。
- 如果预留的空间不够用户线程分配新对象,就会出现一次
Concurrent Mode Failure(并发模式失败)。 - 灾难降临: 发生失败后,JVM 会冻结所有用户线程,退化使用 Serial Old 收集器进行老年代的垃圾回收,这会导致非常漫长的 STW 停顿。
- 如果预留的空间不够用户线程分配新对象,就会出现一次
3. 产生大量的内存碎片
- 原因: CMS 使用的是“标记-清除”算法,这种算法的特性就是只清除垃圾,不整理存活对象,因此收集结束后会产生大量不连续的内存碎片。
- 后果(Promotion Failure): 碎片过多会导致老年代明明还有很多总剩余空间,却无法找到足够大的连续空间来分配大对象。这会导致提前触发 Full GC(或者对象晋升老年代失败
Promotion Failure)。 - 缓解: CMS 提供了
-XX:+UseCMSCompactAtFullCollection参数(默认开启),在 CMS 顶不住要进行 Full GC 时,附带进行一次内存碎片的合并整理过程,但整理内存是需要 STW 的。
三、 CMS 的工作机制总结图
plaintext
用户线程: █████████ ███████████████████ ██████████████████
│ │ │ │
CMS 线程: ████████ ████████
│ │ │ │
STW 并发 STW 并发
初始标记 并发标记 重新标记 并发清除
四、 CMS 的历史地位与现状
- JDK 1.5 - JDK 1.8: 绝对的主力军,特别是在 Web 应用、微服务等对延迟敏感的场景下,通常与年轻代的
ParNew收集器搭配使用。 - JDK 9: 被标记为废弃(Deprecated),官方推荐使用 G1 收集器。
- JDK 14: 正式被移除(Removed)。如果你在 JDK 14+ 中使用
-XX:+UseConcMarkSweepGC,JVM 会报错并拒绝启动。
总结:
CMS 的核心思想是用空间换时间、用 CPU 换延迟,将最耗时的标记和清除过程与用户线程并发执行,从而大幅降低了 STW 时间。虽然它已被历史淘汰,但它确立的“并发标记”思想(如三色标记法)为后来的 G1、ZGC 和 Shenandoah 等现代低延迟垃圾收集器奠定了基础。
右滑查看面试常问