CMS 并发模式失败排查:当老年代碎片过多导致无法承载大对象时,系统如何回退到 Serial Old 收集器
CMS并发模式失败时,因老年代碎片过多无法容纳大对象,JVM会中止并发流程,触发一次由SerialOld收集器执行的FullGC。该收集器采用标记-整理算法,能有效消除碎片,但会导致全程STW及较长停顿。若频繁发生,表明CMS配置可能不足,建议调整触发阈值或考虑迁移至G1等新一代收集器。
CMS 并发模式失败:当老年代碎片过多时,系统如何回退到 Serial Old 收集器

在 CMS 垃圾收集器的世界里,“并发模式失败”(Concurrent Mode Failure)是个需要警惕的信号。一旦发生,意味着老年代因为内存碎片太多,已经无法为大对象找到安身之所。这时,JVM 会果断按下暂停键,中止 CMS 的并发流程,转而触发一次彻底的 Full GC。而执行这次“大扫除”的,正是那个经典而稳重的 Serial Old 收集器。
回退机制的触发条件
当然,并非所有的 CMS 收集失败都会走到 Serial Old 这一步。只有当下面几种情况出现时,这个后备方案才会被激活:
- CMS 正在后台进行标记或清理,突然发现老年代剩下的那点连续空间,根本装不下即将晋升的对象,尤其是那些“大块头”;
- 年轻代 GC 后发生了 晋升失败(Promotion Failed),而老年代此时也已“满目疮痍”,找不到一块完整的空地,CMS 根本来不及反应;
- CMS 的并发收集过程被意外打断(比如遇到了长时间停顿,或者系统资源极度紧张),而此时老年代的占用率早已超过了预设的警戒线(就是那个
-XX:CMSInitiatingOccupancyFraction参数),但完整的一轮收集周期还没走完。
为什么是 Serial Old 而不是其他收集器
你可能会问,为什么偏偏是 Serial Old 来兜这个底?原因其实很清晰:
- 它采用的是 标记-整理(Mark-Compact)算法。这招堪称“碎片终结者”,能在回收后把存活对象规规矩矩地挪到一起,腾出大块的连续空间,彻底解决大对象的分配难题。
- 它是一个单线程、全程 STW(Stop-The-World)的收集器。逻辑简单,行为可预测,作为最后一道保险,它不依赖任何复杂的并发协调机制,反而更可靠。
- 历史原因也很重要。在 JDK 8 及之前的版本里,“CMS + Serial Old”是 HotSpot 虚拟机官方认证的组合,JVM 内部早就把这套降级逻辑给写死了。
如何从日志确认已回退至 Serial Old
想知道系统是不是真的动用了 Serial Old?翻翻 GC 日志就能找到蛛丝马迹。关键线索通常出现在日志的末尾,重点关注收集器的标识和行为特征:
- 在出现
[CMS-concurrent-mark: ...] (concurrent mode failure)这类字样之后,紧接着会看到[Full GC [Tenured: ...]或者[CMS: ...] [Tenured: ...]这样的记录。 - 日志里会明确包含
SerialOld或Serial MarkSweepCompact这样的关键词(具体用词取决于你的 JVM 版本)。 - 这次 GC 的耗时会显著拉长,常常达到几百毫秒甚至数秒。再看
[Times: user=..., real=...]这部分,如果 real(实际耗时)远大于 user(CPU 耗时),那基本可以断定发生了长时间的用户线程暂停。 - 整个过程中,你看不到任何并发阶段的时间统计(比如
CMS-concurrent-preclean),也没有多线程 GC 的线程数信息,因为 Serial Old 是“单干户”。
实际影响与应对建议
必须承认,回退到 Serial Old 本身是一种保护机制,但代价相当高昂:
- 整个堆内存都会被扫描一遍,用户线程完全停止,服务响应出现明显卡顿。
- Serial Old 用单线程处理大堆内存时效率很低,可能引发延迟的雪崩效应。
- 如果这种情况频繁发生,那就不是一个好兆头了。它明确告诉你:当前的 CMS 配置可能已经扛不住应用的实际负载了,是时候考虑优化或者迁移了。
短期内,可以尝试调低 -XX:CMSInitiatingOccupancyFraction 这个参数(比如设为 70),让 CMS 更早一点启动收集,给老年代多留点余量。但从长远来看,更好的选择是评估并切换到像 G1(自带内存整理,抗碎片能力强)或 ZGC 这样的新一代收集器,从根本上避免依赖这种高成本的“兜底”机制。


































