Skip to content

CMS 回收器(原理/特点/参数/弊端)

CMS:第一个并发收集器

CMS(Concurrent Mark Sweep) 是 Java 第一个实现并发标记的垃圾回收器。它的核心目标是:减少 GC 停顿时间。CMS 让大部分 GC 工作与用户线程并发执行,从而大幅缩短停顿时间。

CMS 的工作原理

CMS 的工作过程分为六个阶段,其中只有初始标记和重新标记需要 STW:

CMS 执行阶段:

┌──────────────────────────────────────────────────────┐
│                                                      │
│  1. 初始标记(Initial Mark)── STW ──▶ 短暂停顿       │
│     标记 GC Roots 直接引用的对象                      │
│                                                      │
│  2. 并发标记(Concurrent Mark)─────────▶ 并发执行    │
│     从 GC Roots 出发,追踪所有存活对象               │
│     (用户线程同时运行)                              │
│                                                      │
│  3. 重新标记(Remark)────────── STW ──▶ 修正停顿    │
│     修正并发标记期间产生的变化                       │
│                                                      │
│  4. 并发清除(Concurrent Sweep)────────▶ 并发执行    │
│     清除所有未标记的垃圾对象                         │
│     (用户线程同时运行)                              │
│                                                      │
│  5. 并发重置(Concurrent Reset)────────▶ 并发执行    │
│     重置 CMS 的内部状态                              │
│                                                      │
└──────────────────────────────────────────────────────┘

五阶段的详细解析

阶段一:初始标记(Initial Mark)

java
// 场景:CMS 开始标记
// GC Roots:
//   - 静态变量引用
//   - 线程栈引用
//   - JNI 引用
// 初始标记只标记 GC Roots 直接引用的对象(一级引用)
// 停顿时间很短(通常几十毫秒)
  • 是否 STW:是
  • 停顿时间:短暂
  • 标记内容:GC Roots 直接引用的对象

阶段二:并发标记(Concurrent Mark)

用户线程同时运行:────────────────────────▶ 时间

CMS 线程并发标记:──▶ 标记存活对象 ─────────▶

                    从标记的对象继续追踪
                    二级、三级引用...

注意:并发标记期间,用户线程可能修改引用关系
  • 是否 STW:否(并发执行)
  • 停顿时间:无(但消耗 CPU)
  • 标记内容:从 GC Roots 出发的所有存活对象

阶段三:重新标记(Remark)

并发标记期间,用户线程可能修改了引用关系:

并发标记时,用户线程把对象 B 的引用改成了对象 C:
  Old Obj ──▶ 对象 B ──▶ 被标记为存活

  Old Obj ──▶ 对象 C ──▶ 未被标记(但应该存活!)

重新标记修正这个问题:把所有在并发期间
「指向存活对象却被漏标」的对象重新标记
  • 是否 STW:是
  • 停顿时间:比初始标记长(需要扫描变化区域)
  • 优化:使用 增量更新(Incremental Update) 减少停顿

阶段四:并发清除(Concurrent Sweep)

CMS 线程并发扫描老年代,标记垃圾对象:
  ┌─────────────────────────────────────────┐
  │  [存活] [垃圾] [存活] [垃圾] [存活]   │
  │   ✓        ✓        ✓                  │
  └─────────────────────────────────────────┘
  清除垃圾,释放内存
  • 是否 STW:否(并发执行)
  • 特点:不整理内存,会产生碎片

阶段五:并发重置(Concurrent Reset)

重置 CMS 的内部数据结构,为下一次 GC 做准备。

CMS 的使用

bash
# 启用 CMS 收集器
java -XX:+UseConcMarkSweepGC MyApp

# 年轻代自动使用 ParNew
# -XX:+UseParNewGC 是默认行为(可省略)

CMS 的参数配置

bash
# 触发老年代并发收集的堆使用率阈值
# 默认 68%,即堆使用到 68% 时开始并发标记
java -XX:CMSInitiatingOccupancyFraction=75 MyApp

# 强制在 FullGC 时使用 Serial Old(备选方案)
java -XX:+UseCMSCompactAtFullCollection MyApp
# JDK 9+ 已默认开启

# 设置多少次 FullGC 后压缩老年代
java -XX:CMSFullGCsBeforeCompaction=5 MyApp

# 启用增量模式(减少并发占用的 CPU)
# 适合 CPU 资源有限的场景
java -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode MyApp

CMS 的四大弊端

弊端一:CPU 敏感

CMS 在并发阶段需要消耗 CPU 资源:

阶段是否 STWCPU 占用
初始标记N/A(很短)
并发标记1 个 CPU 核心
重新标记N/A
并发清除1 个 CPU 核心

如果机器 CPU 核心数少(< 4),CMS 可能导致性能下降。

弊端二:内存碎片

CMS 使用标记-清除算法,不整理内存

CMS 并发清除后:
┌─────────────────────────────────────────────┐
│ [存活] [洞洞] [存活] [洞洞洞洞] [存活]        │
│       碎片        大量碎片      碎片         │
└─────────────────────────────────────────────┘

碎片积累后,即使总空闲内存够用,也可能无法分配大对象。

弊端三:浮动垃圾

并发标记期间产生的「浮动垃圾」必须等到下一次 GC 才能回收:

浮动垃圾场景:
1. 并发标记期间,对象 X 原本被标记为存活
2. 标记完成后,用户线程把 X 的引用清空
3. X 现在变成了垃圾,但已经「标记完成」
4. 必须等到下一次 GC 才能回收 X

弊端四:并发模式失败(Concurrent Mode Failure)

最严重的问题:并发模式失败

并发模式失败场景:
CMS 并发执行中...

    │ 老年代满了,但 CMS 无法完成并发收集
    │ (并发期间有太多新对象晋升到老年代)


JVM 紧急切换到 Serial Old(MSC)

    │ Serial Old 是单线程标记-压缩
    │ 停顿时间可能达到几秒!


程序长时间停顿,性能急剧下降

CMS 的演进与淘汰

CMS 历史:
2014 ──→ JDK 9:CMS 标记为废弃
2017 ──→ JDK 9:G1 成为默认 GC
2018 ──→ JDK 14:CMS 正式移除

替代方案

替代说明
G1JDK 9 默认,平衡吞吐和延迟
ZGCJDK 11+,亚毫秒级停顿
ShenandoahJDK 12+,类似 ZGC

CMS 的 GC 日志

bash
java -XX:+UseConcMarkSweepGC \
     -XX:+PrintGCDetails \
     MyApp

日志示例:

# 初始标记
2026-03-22T10:00:00.000: [GC (CMS Initial Mark)
  [CMS-initial-mark: 1048576K/2097152K]
  1048576K->1048576K(2097152K), 0.0123456 secs]
  [Times: user=0.01 sys=0.00, real=0.01 secs]

# 并发标记
# (并发阶段没有日志)

# 重新标记
2026-03-22T10:00:00.123: [GC (CMS Final Remark)
  [CMS-remark: 1048576K->1048576K(2097152K)]
  0.0234567 secs]
  [Times: user=0.03 sys=0.01, real=0.02 secs]

# 并发清除
# (并发阶段没有日志)

本节小结

CMS 收集器的核心要点:

维度说明
设计目标低停顿(并发收集)
算法标记-清除
阶段初始标记 → 并发标记 → 重新标记 → 并发清除
停顿时间仅初始标记 + 重新标记(短暂)
弊端CPU 敏感、内存碎片、浮动垃圾、并发失败
JDK 状态JDK 14 移除

CMS 是 JVM GC 历史上的里程碑——它是第一个实现并发收集的回收器。但它的缺陷(碎片、并发失败)最终导致它被 G1 和 ZGC 取代。

下一节,我们来看现代 GC 的代表——G1 回收器(核心)

基于 VitePress 构建