跳转至

CMS:已被移除的并发收集器(JDK 8→升级必读)#

版本信息

  • 全称:Concurrent Mark Sweep(并发标记-清除)
  • 生命周期:JDK 1.4.1 试验引入 → JDK 5 正式 → JEP 291 JDK 9 弃用JEP 363 JDK 14 移除
  • 当前状态:❌ 已移除。JDK 14 起 -XX:+UseConcMarkSweepGCUnrecognized VM option
  • MXBean 名(仅 JDK ≤13):ParNew(年轻代)/ ConcurrentMarkSweep(老年代)

如果你维护的 JDK 8 服务还配着 -XX:+UseConcMarkSweepGC升级到 JDK 14+ 会直接启动失败。这一页回答三件事:CMS 当初为什么受欢迎、它为什么被干掉、升级时该换成什么。

设计目标:降低老年代回收停顿#

在 CMS 之前,老年代回收(Full GC)是全 STW的整堆操作,停顿随堆增长。CMS(JDK 5 引入)首次把老年代的大部分回收工作与应用线程并发进行,显著降低了单次停顿——在那个年代,它是低延迟的代名词。但它从未成为默认,需手动 -XX:+UseConcMarkSweepGC 启用。

为什么被移除#

CMS 是 HotSpot 历史上最复杂的 GC之一,被淘汰有三个硬原因:

  1. 碎片化(致命伤):CMS 用标记-清除(mark-sweep),不整理老年代。长期运行后老年代布满碎片,没有足够连续空间容纳晋升对象 → 触发 promotion failure;或老年代在 CMS 周期跑完前就满了 → concurrent mode failure。两者都会退化成 Serial Old 的整堆 Full GC——停顿比它要降低的还长得多,彻底违背低停顿初衷。
  2. 代码复杂、维护成本高:CMS 的并发标记、屏障、 Remembered Set 与新 GC(G1/ZGC/Shenandoah)的并发基础设施大量重复且互相耦合,成了 HotSpot 的维护包袱。
  3. G1 已成熟可替代:G1 在 JDK 9 成为默认,region 化 + 整理既停顿可控又无碎片化,足以替代 CMS 的绝大多数场景。CMS 失去了存在意义。

连带移除:CMS 的年轻代收集器 ParNew-XX:+UseParNewGC)只能和 CMS 搭配,CMS 移除后它也在 JDK 14 一同消失。

工作原理:六阶段,大部分并发#

graph TD
    IM["① 初始标记 STW<br/>标记 GC 根直接引用"] --> CM["② 并发标记<br/>与应用线程同时跑"]
    CM --> PC["③ 预清理(并发)"]
    PC --> AP["④ 可中止预清理(并发)"]
    AP --> RM["⑤ 重新标记 STW<br/>修正并发期的引用变化"]
    RM --> SW["⑥ 并发清除<br/>回收不可达对象"]
    SW --> F{"碎片化 / 空间不足?"}
    F -->|"否(多数周期)"| OK["正常运行<br/>进入下一周期"]
    F -->|"promotion failure / concurrent mode failure"| FF["退化:Serial Old Full GC<br/>长停顿,整堆整理"]
    style IM fill:#ffe0b2
    style RM fill:#ffe0b2
    style OK fill:#c8e6c9
    style FF fill:#ffcdd2

只有 ① 初始标记⑤ 重新标记 两个短点是 STW,其余 ②③④⑥ 都与应用并发——这是 CMS 低停顿的来源。但代价是 ⑥ 不整理 → 碎片化 → 退化的长 Full GC(图右路径)。

JVM 配置参数(仅 JDK ≤13 有效)#

参数 含义 默认 备注
-XX:+UseConcMarkSweepGC 启用 CMS(年轻代用 ParNew) 关(非默认) JDK 14 起已移除,启用即报错
-XX:CMSInitiatingOccupancyFraction=<n> 老年代占用百分比达 n% 触发 CMS 周期 自适应(≈92%) 调小让 CMS 更早启动,缓解空间压力
-XX:+UseCMSInitiatingOccupancyOnly 禁用自适应,固定用上面百分比 让触发时机可预测
-XX:+CMSParallelRemarkEnabled 重新标记阶段并行化 缩短 ⑤ 的 STW
-XX:+CMSScavengeBeforeRemark 重新标记前先做一次年轻代 GC 减少跨代引用扫描,缩短 ⑤
-XX:CMSMaxAbortablePrecleanTime=<ms> 可中止预清理的最长等待 5000 控制何时进入重新标记
# 仅 JDK 8/9~13 有效;JDK 14+ 会报 Unrecognized VM option
java -XX:+UseConcMarkSweepGC -Xmx4g \
     -XX:CMSInitiatingOccupancyFraction=70 \
     -XX:+UseCMSInitiatingOccupancyOnly \
     -XX:+CMSScavengeBeforeRemark \
     -jar app.jar

升级怎么办:换成 G1 或 ZGC#

CMS 已无继任意义上的「同款」——直接按需求换:

原 CMS 诉求 现代替代 启用
「停顿可控、通用大堆」 G1(默认) 不用配,或 -XX:+UseG1GC -XX:MaxGCPauseMillis=200
「更极致的低延迟、大堆」 ZGC -XX:+UseZGC -Xmx16g
「低延迟 + Red Hat 系发行版」 Shenandoah -XX:+UseShenandoahGC

实操:升级前先把所有 -XX:+UseConcMarkSweepGC-XX:+UseParNewGC 及 CMS 专属调优标志全部删掉,让 JVM 用默认 G1;再按停顿/吞吐实测决定是否上 ZGC。

与前代 GC 对比:Parallel → CMS(STW → 老年代并发)#

CMS 相对当时的默认 Parallel,把老年代回收并发化——Parallel 的 Full GC 是全 STW 整堆、停顿随堆增长;CMS 把大部分老年代回收放到应用线程并发执行,停顿显著降低。代价是不整理 → 碎片化,最终被 G1 取代并移除。

java -XX:+UseParallelGC -Xmx8g -jar app.jar   # 老年代 Full GC 整堆 STW,停顿长
java -XX:+UseConcMarkSweepGC -Xmx4g -jar app.jar   # 老年代并发、停顿短,但不整理→碎片化

CMS 生命周期:JDK 8 仍可用 → JDK 9(JEP 291)弃用 → JDK 14(JEP 363移除-XX:+UseConcMarkSweepGCUnrecognized VM option)。它换来的低停顿被 G1 / ZGC 更好地继承。

常见坑 / 最佳实践#

  • 碎片化是 CMS 的本质问题,调参只能延缓-XX:CMSInitiatingOccupancyFraction 调小能让 CMS 更早跑、减少空间压力,但治标不治本,长期仍会碎片化并退化。
  • 升级脚本务必清理 CMS 标志:JDK 14+ 启动会直接失败;别等到生产报错才发现。
  • CMS 的「低停顿」是相对时代的:相比今天的 ZGC 亚毫秒停顿,CMS 的停顿毫秒到几十毫秒,且退化时反而很长。
  • ParNew 也没了-XX:+UseParNewGC 与 CMS 同命运,JDK 14 起同样 Unrecognized
  • 别在 JDK 8 用 CMS 的脚本硬迁:CMS 的调优标志(CMSInitiatingOccupancyFraction 等)在 G1/ZGC 上无效甚至有害,迁移时一并清除,重新按新 GC 的语义调。

小结#

CMS 是 Java 走向并发低延迟回收的先驱,功不可没;但「标记-清除不整理 → 碎片化 → 退化长 Full GC」是它的结构性死穴,加之维护成本高、G1 已成熟,最终在 JDK 14 被移除。还在用 CMS 的 JDK 8 服务,升级时请果断切到 G1(默认)或 ZGC

参考#