CMS垃圾回收器解析

一、CMS垃圾回收器简介

CMS(Concurrent Mark-Sweep)是JVM中最早实现并发垃圾回收的收集器之一,主要目标是最小化应用停顿时间,适合对响应延迟敏感的服务端应用(如Web服务器、在线交易系统等)。

  • 适用JVM参数-XX:+UseConcMarkSweepGC
  • 主要针对老年代进行回收(新生代默认用ParNew)

二、工作原理与生命周期

CMS采用“标记-清除”算法,核心思想是让大部分GC工作与应用线程并发执行,从而减少Stop-The-World(STW)停顿。

1. 回收流程

CMS老年代回收大致分为4个阶段:

  1. 初始标记(Initial Mark)

    • STW短暂停顿
    • 标记所有GC Roots直接可达对象
  2. 并发标记(Concurrent Mark)

    • 与应用线程并发
    • 从GC Roots出发,扫描对象图,标记所有可达对象
  3. 重新标记(Remark)

    • STW,时间较短
    • 处理并发标记期间新产生的引用变动(通过“增量更新”或“原始快照”)
  4. 并发清除(Concurrent Sweep)

    • 与应用线程并发
    • 回收所有未被标记的对象,释放空间

注意: 新生代通常由ParNew(多线程)回收,老年代由CMS回收。


三、算法细节与实现机制

1. 标记-清除算法

  • 标记阶段:遍历对象图,标记所有可达对象。
  • 清除阶段:回收未被标记的对象,空间可能产生碎片。

2. 并发与STW

  • 只有初始标记和重新标记需要STW,时间短。
  • 并发标记、并发清除与应用线程并发,减少应用停顿。

3. 写屏障与卡表

  • 为了支持并发标记,JVM采用写屏障(Write Barrier)机制记录对象引用的变动。
  • 典型实现有“增量更新(Incremental Update)”和“原始快照(Snapshot-at-the-Beginning)”。

4. 空间碎片问题

  • CMS采用清除而非压缩,容易产生老年代碎片,可能导致“Promotion Failed”或“Full GC”。

四、优缺点分析

优点

  • 低延迟:STW时间短,适合对响应时间敏感的应用。
  • 高并发:大部分GC工作与应用线程并发进行。

缺点

  • 空间碎片:采用标记-清除,老年代易碎片化,严重时触发Full GC。
  • 并发失败:如果回收速度跟不上分配速度,可能“Concurrent Mode Failure”,导致Full GC,长时间STW。
  • CPU资源消耗大:并发阶段与应用线程争抢CPU,整体吞吐量下降。
  • 已被G1等新一代GC替代:JDK9以后已标记为“即将废弃”。

五、常用JVM参数与调优

1. 启用CMS

-XX:+UseConcMarkSweepGC

2. 配合ParNew新生代收集器

-XX:+UseParNewGC

3. 控制并发线程数

-XX:ParallelCMSThreads=4   # CMS并发线程数
-XX:ConcGCThreads=4        # 并发GC线程数(JDK8+)

4. 空间碎片与Full GC调优

  • 自动空间压缩(默认关闭)
    -XX:+UseCMSCompactAtFullCollection
    Full GC时对老年代进行压缩,减少碎片,但会增加停顿。

  • 触发压缩的Full GC阈值
    -XX:CMSFullGCsBeforeCompaction=2
    每2次Full GC后进行一次压缩。

5. 回收阈值与触发时机

  • 老年代使用率阈值
    -XX:CMSInitiatingOccupancyFraction=70
    老年代使用率达到70%时触发CMS回收。

  • 允许在CMS期间分配失败自动转Full GC
    -XX:+CMSFullGCsBeforeCompaction

6. 其他常用参数

  • -XX:+CMSScavengeBeforeRemark:在Remark阶段前进行一次Minor GC,提升回收效率。
  • -XX:+CMSClassUnloadingEnabled:允许卸载无用类元数据,减少Metaspace压力。

六、常见问题与工程实践

1. Concurrent Mode Failure

  • 回收速度跟不上分配速度,老年代被填满,JVM被迫触发Full GC(STW时间长)。
  • 解决方法:
    • 提前触发CMS(降低CMSInitiatingOccupancyFraction)
    • 增大老年代空间
    • 优化应用对象分配和生命周期

2. Promotion Failed

  • 新生代晋升到老年代失败,可能因碎片或空间不足。
  • 解决方法:
    • 增大老年代
    • 调整分区比例
    • 优化对象分代策略

3. 碎片导致频繁Full GC

  • CMS不压缩空间,老年代碎片多时大对象分配失败。
  • 解决方法:
    • 启用Full GC时压缩
    • 及时升级到G1等支持并发压缩的GC

4. CPU资源竞争

  • CMS并发阶段与应用线程争抢CPU,业务高峰期可能影响吞吐。
  • 解决方法:
    • 合理设置并发线程数
    • 评估业务高峰期GC影响

七、GC日志分析

开启GC日志:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log

CMS日志关键字段:

  • CMS-initial-mark:初始标记
  • CMS-concurrent-mark:并发标记
  • CMS-remark:重新标记
  • CMS-concurrent-sweep:并发清除
  • Full GC:Full GC发生
  • concurrent mode failure:并发回收失败

八、CMS与其他GC的对比

GC收集器适用场景优势劣势
CMS低延迟服务端停顿短、并发碎片、Full GC慢
G1大堆、低延迟并发+压缩、可预测配置复杂、吞吐略低
Parallel吞吐优先吞吐高停顿长
ZGC/Shenandoah超大堆、极低延迟停顿极短、并发压缩JDK11+/新、成熟度有限

九、结论与建议

  • CMS适合对响应时间敏感堆空间中等的服务端应用。
  • 生产环境要重点关注碎片、Full GC、并发失败等问题。
  • JDK9以后建议优先采用G1或ZGC等新一代GC。
  • 持续关注GC日志,结合业务负载动态调优。

十、参考资料

十一、写屏障(Write Barrier)与卡表(Card Table)

1. 为什么需要写屏障?

在CMS的并发标记阶段,应用线程和GC线程同时运行。如果应用线程在GC标记时修改了对象引用(即“脏写”),会导致GC漏标记,造成误回收。

2. 写屏障机制

CMS采用写屏障(Write Barrier)机制拦截所有对象引用的写操作。当应用线程修改引用时,将被修改的“卡片”(Card)标记为脏(dirty),以便在重新标记(Remark)阶段重新扫描。

  • 增量更新(Incremental Update):标记所有在并发标记后被修改过的对象。
  • 原始快照(Snapshot-at-the-Beginning):标记所有在并发标记前已存在的引用。

CMS默认采用增量更新

3. 卡表(Card Table)

  • 堆被划分为固定大小的“卡片”(通常512字节)。
  • 每个卡片有一个状态位,记录是否被修改。
  • 写屏障会把对应卡片的状态置为脏。

十二、并发标记的难点与优化

1. 并发标记的挑战

  • 应用线程持续创建和修改对象,GC线程很难捕捉所有变化。
  • 写屏障和卡表机制保证了可达性分析的正确性,但带来一定性能开销。

2. 重新标记(Remark)阶段

  • 由于并发期间有新引用产生,CMS需要STW下重新遍历脏卡片,确保所有可达对象都被正确标记。
  • 这一步虽然STW,但比全表扫描快很多。

3. 与新生代GC的协作

  • 在Remark前通常会触发一次Minor GC,减少新生代对老年代的引用变动。

十三、碎片问题底层分析

1. 标记-清除的本质

  • CMS只回收不可达对象,不移动存活对象。
  • 多次GC后,老年代会出现大量不连续的小空洞(碎片)。

2. 碎片的实际危害

  • 大对象分配时,虽然总空间足够,但没有足够大的连续内存,导致分配失败(Promotion Failed)。
  • JVM被迫触发Full GC,并进行内存压缩,造成长时间STW。

3. Full GC与压缩

  • Full GC会暂停所有线程,移动存活对象,合并碎片,但代价大。
  • CMS可通过参数定期触发压缩,但会丧失低延迟优势。

十四、参数调优实战

1. 常用参数说明

参数作用推荐设置
-XX:+UseConcMarkSweepGC启用CMS必须
-XX:CMSInitiatingOccupancyFraction=70老年代使用率达到70%时触发CMS50-80,根据业务负载调整
-XX:+UseCMSCompactAtFullCollectionFull GC时压缩老年代建议开启
-XX:CMSFullGCsBeforeCompaction=2每2次Full GC后压缩一次1-5,视碎片情况调整
-XX:+CMSClassUnloadingEnabled允许卸载无用类元数据建议开启
-XX:+CMSScavengeBeforeRemarkRemark前先做一次Minor GC建议开启
-XX:ParallelCMSThreads并发GC线程数一般为CPU数/2或CPU数

2. 实践建议

  • 内存充裕时,适当增大老年代,减少GC频率。
  • 延迟敏感时,降低CMSInitiatingOccupancyFraction,提前回收,避免Concurrent Mode Failure。
  • 碎片严重时,调低CMSFullGCsBeforeCompaction,增加压缩频率。

十五、GC日志精细解读

示例日志片段:

2019-01-01T12:00:00.000+0800: 1.234: [GC (CMS Initial Mark) [1 CMS-initial-mark: 12345K(24576K)] 23456K(32768K), 0.0123456 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2019-01-01T12:00:00.500+0800: 1.734: [GC (CMS Final Remark) [YG occupancy: 4096 K (8192 K)] 16384K(24576K), 0.0234567 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2019-01-01T12:00:01.000+0800: 2.234: [GC (CMS Concurrent Sweep) ...]
2019-01-01T12:00:05.000+0800: 6.234: [Full GC (Allocation Failure) ...]
  • Initial Mark:初始标记,STW
  • Concurrent Mark:并发标记
  • Final Remark:重新标记,STW
  • Concurrent Sweep:并发清除
  • Full GC:分配失败,STW极长

关注点:

  • Remark和Initial Mark耗时,是否过长
  • 是否频繁出现Full GC
  • 是否有Concurrent Mode Failure

十六、与G1/ZGC的迁移建议

1. 为什么迁移?

  • CMS已被标记为废弃(JDK14+),后续不会维护
  • G1支持并发标记+并发压缩,几乎无碎片问题
  • ZGC、Shenandoah支持超大堆、极低延迟

2. 迁移步骤

  1. 评估业务延迟和吞吐需求
  2. JVM参数切换
    • G1:-XX:+UseG1GC
    • ZGC:-XX:+UseZGC(JDK11+)
  3. 调优目标停顿时间
    • G1:-XX:MaxGCPauseMillis=200
    • ZGC:-XX:MaxPauseMillis=10
  4. 观察GC日志,调整堆大小、线程数、分区数

3. 兼容性与坑

  • G1参数与CMS有差异,需重新调优
  • ZGC对JDK版本有要求,部分监控工具尚不完善

十七、工程实践总结

  1. CMS适合对延迟敏感、堆空间不太大的中大型服务。
  2. 生产环境务必监控GC日志,重点关注碎片、Full GC、并发失败。
  3. 新项目建议优先选择G1/ZGC,老项目逐步迁移,CMS仅用于特殊场景。
  4. GC调优是动态过程,需结合业务压力、内存分配、对象生命周期持续优化。

十八、参考与工具

十九、底层实现与源码机制

1. CMS 源码核心流程(以 OpenJDK 为例)

CMS 在 HotSpot 源码中主要通过 ConcurrentMarkSweepGeneration 类实现,核心流程如下:

  • Initial Mark:通过遍历 GC Roots(线程栈、本地变量、静态变量等),标记直接可达对象。
  • Concurrent Mark:通过多线程并发遍历对象图,递归标记所有可达对象。采用任务队列分配给多个 GC 线程。
  • Preclean:在 Remark 前,对新生代晋升到老年代以及写屏障记录的变更做一次预处理,减少 Remark 工作量。
  • Remark:STW,处理并发期间未被及时标记的对象,通常采用增量更新算法。
  • Concurrent Sweep:并发清除未标记对象,回收空间。
  • Reset:重置标记位,为下次 GC 做准备。

2. 写屏障源码简析

写屏障在 CMS 主要通过 CardTableRS(Card Table Remembered Set)实现。每次对象引用被写入时,都会在卡表中记录该内存区域为“脏”,以便后续 Remark 阶段重新扫描。


二十、典型故障场景与排查

1. Concurrent Mode Failure

现象:GC 日志频繁出现 [GC (CMS) ... (concurrent mode failure)],应用长时间停顿。

原因

  • 老年代分配速度远快于 CMS 回收速度。
  • CMS 触发太晚或并发线程数不足。
  • 业务高峰期对象晋升过快。

排查方法

  • 检查 CMSInitiatingOccupancyFraction 是否过高。
  • 检查 GC 日志中 CMS 开始与 Full GC 的时间间隔。
  • 观察老年代分配速率与回收速率。

2. Promotion Failed

现象:新生代对象晋升到老年代失败,导致 Full GC。

原因

  • 老年代碎片严重,无法分配大对象。
  • 老年代空间不足。

排查方法

  • 观察 GC 日志中 Promotion failedallocation failure 相关字段。
  • 用 jmap -heap 或监控工具查看老年代碎片比例、最大可用连续空间。

3. Remark 阶段耗时过长

现象:GC 日志中 CMS-remark 耗时显著增加。

原因

  • 写屏障记录的变更过多,脏卡片数量激增。
  • 新生代与老年代引用关系复杂。

排查方法

  • 业务代码是否有大量对象间引用变动。
  • GC 日志分析卡表扫描量。

二十一、与 JVM 内存模型的关系

  • CMS 主要负责老年代(Tenured Generation),新生代由 ParNew 或其他收集器负责。
  • 老年代空间配置过小或分代比例不合理,会加剧 CMS 的压力。
  • 元空间(Metaspace/Class Metadata)可用 CMSClassUnloadingEnabled 参数控制卸载,防止类元数据泄漏。

二十二、监控与自动化工具

1. GC 日志分析

  • GCeasy.io:在线 GC 日志分析,支持 CMS、G1、ZGC 等。
  • JClarity Censum:企业级 GC 分析工具。
  • VisualVM/JMC:JVM 监控与堆分析。

2. 自动化调优建议

  • 定期分析 GC 日志,检测 Full GC、碎片、并发失败等异常模式。
  • 配合监控系统(Prometheus/Grafana)设置 GC 停顿时间、老年代使用率告警。

二十三、业务性能影响分析

  • 延迟敏感业务(如 Web 服务、交易系统):CMS 可极大减少平均停顿时间,但碎片和 Full GC 可能导致偶发长时间卡顿。
  • 吞吐优先业务(如批处理、大数据):CMS 并发线程争抢 CPU,可能影响整体吞吐,建议用 Parallel 或 G1。
  • 高并发场景:CMS 的并发标记与清除对 CPU 资源消耗大,需合理配置线程数。

二十四、迁移到 G1/ZGC 的实战经验

1. 迁移步骤

  1. 评估业务需求:确定对延迟、吞吐、堆空间的要求。
  2. 切换 GC 参数:如 -XX:+UseG1GC 或 -XX:+UseZGC,并设置目标停顿时间。
  3. 调优分区和线程数:G1 可调 -XX:MaxGCPauseMillis, ZGC 可调 -XX:ConcGCThreads
  4. 监控与回归测试:观察 GC 日志,确认 Full GC、停顿时间、吞吐率是否达标。

2. 迁移坑点

  • G1 的分区机制与 CMS 不同,参数需重新调优。
  • ZGC 需 JDK11+,部分应用和监控工具可能不兼容。
  • 业务高峰期需重点关注 GC 停顿分布,避免偶发长时间 STW。

二十五、生产事故排查经验

  • 碎片导致 OOM:建议定期 Full GC 压缩、或提前迁移到 G1。
  • 并发失败频繁:调低 CMS 启动阈值,增加并发线程,优化对象分配。
  • GC日志丢失信息:确保日志参数完整,定期归档分析。
  • 业务高峰卡顿:GC线程与业务线程合理分配,避免资源抢占。

二十六、总结

CMS 作为经典低延迟 GC,适合响应敏感但堆空间不极大的服务。其并发标记、写屏障、卡表机制能有效减少停顿,但碎片和 Full GC 是其最大短板。生产环境需结合业务场景、内存分配、GC日志持续调优,必要时迁移到 G1/ZGC。合理监控与自动化分析,是保障系统稳定的关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值