文章目录

Java CMS垃圾收集器的浮动垃圾与Concurrent Mode Failure

发布于 2026-04-20 02:26:26 · 浏览 7 次 · 评论 0 条

Java CMS垃圾收集器的浮动垃圾与Concurrent Mode Failure

CMS(Concurrent Mark Sweep)收集器以获取最短回收停顿时间为目标,允许垃圾收集线程与用户线程并发执行。这种设计虽然降低了停顿时间,但也引入了“浮动垃圾”和“Concurrent Mode Failure”这两个典型问题。处理不当会导致系统性能急剧下降,甚至出现长时间的服务不可用。


浮动垃圾的产生与影响

CMS的并发标记和并发清理阶段,用户线程依然在运行,程序自然会不断产生新的垃圾对象。这部分垃圾出现在标记过程结束之后,CMS无法在当次收集中处理它们,只能留在下次垃圾收集时清理。这就叫作浮动垃圾。

由于垃圾收集和用户线程并发执行,CMS不能像其他收集器那样等到老年代几乎填满才进行收集,必须预留一部分空间供并发期间用户线程分配新对象使用。

如果预留的空间不足以存放并发期间产生的浮动垃圾,就会触发严重后果。

graph TD A["CMS Concurrent Phase Start"] --> B["User Threads Running"] B --> C["New Objects Created (Floating Garbage)"] B --> D["Old Generation Filling Up"] D --> E{"Reserved Space Enough?"} E -->|Yes| F["CMS Completes Successfully"] E -->|No| G["Concurrent Mode Failure"] G --> H["Stop The World"] H --> I["Serial Old GC (Full GC)"]

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 和内存碎片导致的长时间停顿。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文