这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 CMS(Concurrent Mark Sweep)曾是主流低停顿老年代收集器,目标是让大部分标记和清理与应用并发执行。但它有内存碎片、并发失败、浮动垃圾和参数复杂等问题,HotSpot 在 Java 14 中移除了 CMS。
16.1 历史定位
Serial / Parallel
-> 停顿随老年代增大变长
-> CMS 引入并发标记清除
-> G1 / ZGC / Shenandoah 继续演进
Java 版本变化:
| 版本 | 变化 |
|---|---|
| Java 8 | CMS 可用 |
| Java 9 | CMS 被标记 deprecated |
| Java 14 | CMS 被移除 |
仍在使用 CMS 的系统通常是老 JDK 或存量平台,应规划升级。
16.2 工作阶段
Initial Mark
-> Concurrent Mark
-> Concurrent Preclean
-> Final Remark
-> Concurrent Sweep
-> Concurrent Reset
| 阶段 | STW | 说明 |
|---|---|---|
| Initial Mark | 是 | 标记根直接可达对象 |
| Concurrent Mark | 否 | 遍历对象图 |
| Preclean | 否 | 预处理并发期间变化 |
| Final Remark | 是 | 处理剩余引用 |
| Concurrent Sweep | 否 | 清理死亡对象 |
| Concurrent Reset | 否 | 重置状态 |
16.3 优缺点
优点:
- 老年代标记清除大部分并发;
- 低停顿优于 Parallel Full GC;
- 适合当时交互服务。
缺点:
- mark-sweep 产生碎片;
- 并发阶段占用 CPU;
- 浮动垃圾;
- 需要预留空间;
- concurrent mode failure 会退化 Serial Old;
- 参数复杂;
- 维护成本高。
16.4 内存碎片
清除后空闲块不连续
-> 总空闲足够
-> 无法分配大对象
-> 触发 Full GC / 压缩
碎片常见诱因:
- 大对象;
- 长期运行;
- 对象大小差异大;
- 晋升频繁;
- 碎片整理不及时。
CMS 有增量整理策略,但难以彻底消除碎片问题。
16.5 并发失败
常见日志概念:
concurrent mode failure
promotion failed
原因:
- 老年代预留不足;
- 晋升过快;
- 碎片导致空间不可用;
- 并发回收跟不上分配;
- CPU 不足。
后果通常是退化到更重停顿的 Full GC。
16.6 常见参数
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSMaxAbortablePrecleanTime=5000
-XX:CMSFullGCsBeforeCompaction=0
这些参数只适用于仍支持 CMS 的 JDK。设置 CMSInitiatingOccupancyFraction 太高会增加并发失败风险,太低会频繁并发周期。
16.7 与 G1 对比
| 项 | CMS | G1 |
|---|---|---|
| 堆布局 | 连续 Old 分代 | Region 化 |
| 算法 | 标记清除 | 复制 / 整理 |
| 碎片 | 明显 | 通常更低 |
| 停顿可控 | 弱 | PausePredictionModel |
| 大对象 | 直接 Old | Humongous Region |
| 维护 | 已移除 | 现代 JDK 持续演进 |
从 CMS 迁移到 G1:
- 升级 JDK;
- 保留 GC 日志和指标基线;
- 压测对比;
- 关注大对象和晋升;
- 检查内存占用变化;
- 分批灰度;
- 保留回滚。
16.8 排障思路
查看 GC 日志:
1. CMS 周期频率
2. Initial / Final Remark 耗时
3. Old 回收量
4. 晋升量
5. concurrent mode failure
6. Full GC 原因
处理:
| 现象 | 方向 |
|---|---|
| 并发周期频繁 | 分配高或阈值低 |
| final remark 长 | 引用处理多 |
| promotion failed | Survivor / Old 不足 |
| concurrent failure | 预留不足或 CPU 不足 |
| 碎片化 | 大对象和长期运行 |
16.9 面试要点
CMS 常考问题:
- 初始标记和最终标记为什么 STW;
- 并发标记如何处理引用变化;
- 为什么会有浮动垃圾;
- 为什么 mark-sweep 会碎片化;
- 为什么并发失败停顿长;
- 为什么 CMS 被移除。
回答时应说明它是特定历史阶段的折中设计,而不是把 CMS 当作现代默认选择。
本章小结
CMS 用并发标记清除降低老年代回收停顿,但代价是碎片、浮动垃圾、CPU 竞争和并发失败风险。它已在 Java 14 被移除,现代 Java 服务应优先评估 G1、ZGC 或 Shenandoah。理解 CMS 有助于理解低停顿收集器的演进。
思考题
- CMS 哪些阶段需要 STW?
- 为什么 CMS 会产生浮动垃圾?
- concurrent mode failure 的常见原因是什么?
- CMS 为什么会产生内存碎片?
- 从 CMS 迁移到 G1 要验证哪些指标?