Java CMS垃圾收集器的浮动垃圾与Concurrent Mode Failure
CMS(Concurrent Mark Sweep)收集器以获取最短回收停顿时间为目标,允许垃圾收集线程与用户线程并发执行。这种设计虽然降低了停顿时间,但也引入了“浮动垃圾”和“Concurrent Mode Failure”这两个典型问题。处理不当会导致系统性能急剧下降,甚至出现长时间的服务不可用。
浮动垃圾的产生与影响
CMS的并发标记和并发清理阶段,用户线程依然在运行,程序自然会不断产生新的垃圾对象。这部分垃圾出现在标记过程结束之后,CMS无法在当次收集中处理它们,只能留在下次垃圾收集时清理。这就叫作浮动垃圾。
由于垃圾收集和用户线程并发执行,CMS不能像其他收集器那样等到老年代几乎填满才进行收集,必须预留一部分空间供并发期间用户线程分配新对象使用。
如果预留的空间不足以存放并发期间产生的浮动垃圾,就会触发严重后果。
Concurrent Mode Failure 的后果
当预留的空间不够用,或者CMS运行期间用户请求分配的对象速度过快,导致老年代剩余空间不足时,JVM会报错 Concurrent Mode Failure。
此时,JVM会启动后备预案:冻结所有用户线程的执行,临时启用 Serial Old 收集器重新进行老年代的垃圾收集。Serial Old 是单线程的,会导致长时间的 Stop-The-World (STW),严重影响系统响应速度。
此外,CMS使用“标记-清除”算法,会产生大量内存碎片。当碎片过多时,即使老年代剩余空间总量足够,也无法找到一块连续区域存放新的大对象,这也会提前触发 Full GC。
核心参数配置指南
为了避免 Concurrent Mode Failure 并减少内存碎片的影响,需要合理调整JVM启动参数。以下是关键参数的说明与配置建议。
| 参数名称 | 默认值 | 作用说明 | 建议配置 |
|---|---|---|---|
-XX:CMSInitiatingOccupancyFraction |
92% (JDK 6+) | 老年代使用率达到该百分比时触发CMS GC。 | 70% ~ 80% |
-XX:+UseCMSCompactAtFullCollection |
开启 | 在Full GC时是否进行内存碎片的合并整理(标记-整理)。 | 保持开启 |
-XX:CMSFullGCsBeforeCompaction |
0 | 设置执行多少次不压缩的Full GC后,跟着来一次带压缩的。 | 0 (每次都整理) |
-XX:+UseCMSInitiatingOccupancyOnly |
关闭 | 限制JVM仅使用设定的阈值启动CMS,禁止JVM自行根据运行时数据调整。 | 高负载时开启 |
解决步骤与调优实战
针对 CMS 产生的浮动垃圾和并发失败问题,按照以下步骤进行排查和调优。
1. 识别问题日志
检查 GC日志文件,查找是否存在 Concurrent Mode Failure 关键字。如果日志中出现了该错误,说明在CMS并发回收过程中,对象分配速度超过了回收速度,或者由于内存碎片导致分配失败。
2. 调整触发阈值
降低 CMS启动的阈值,让垃圾收集器更早开始工作,从而预留更多空间给并发期间产生的浮动垃圾。
在启动脚本中添加或修改参数:
-XX:CMSInitiatingOccupancyFraction=75
这表示老年代使用率达到 75% 时就触发CMS GC,而不是等到默认的 92%。虽然这会增加GC的频率,但能有效降低 Concurrent Mode Failure 的概率。
3. 强制进行碎片整理
由于“标记-清除”算法会产生碎片,配置参数确保在Full GC时对内存进行压缩整理。
设置以下参数:
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
这表示每次进行 Full GC 时都进行内存碎片的整理。虽然整理过程是单线程的,会暂停应用,但能保证内存空间的连续性,避免因碎片导致的意外 Full GC。
4. 优化 System.gc() 策略
如果业务允许在低峰期进行清理,可以利用代码手动触发 GC,但必须注意参数配合。
移除 JVM 参数中的 -XX:+DisableExplicitGC,允许代码中调用 System.gc()。
检查 是否存在 -XX:+ExplicitGCInvokesConcurrent 参数。
- 如果存在,调用
System.gc()会触发并发 CMS,速度较快但可能不整理碎片。 - 如果不存在(推荐此场景),调用
System.gc()会触发单线程 Full GC,虽然暂停时间长,但会进行内存整理。
在业务低峰期(例如凌晨3点),通过定时任务调用 System.gc() 来主动清理碎片。为了避免集群中所有服务器同时触发GC,增加随机延迟或使用分布式锁。
5. 增大堆内存或优化代码
如果上述参数调整后问题依旧,考虑从根本上解决资源瓶颈。
增加 老年代的最大堆内存大小(调整 -Xmx 和 -XX:MaxNewSize),提供更大的缓冲池。
审查 代码,减少 大对象的直接分配。大对象直接进入老年代会迅速消耗预留空间,增加 CMS 的压力。
总结配置清单
针对高并发且对停顿敏感的系统,推荐使用以下参数组合作为基准进行测试:
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
这套配置通过提前触发GC(75%阈值)并保证每次Full GC都整理碎片,最大程度避免了 Concurrent Mode Failure 和内存碎片导致的长时间停顿。

暂无评论,快来抢沙发吧!