CMS回收失败后必然触发Serial Old降级,这是HotSpot JVM硬编码的确定性行为:因CMS与Parallel Old内存模型不兼容,且Serial Old的标记-整理算法可压缩碎片、恢复大对象分配能力,其单线程STW特性在紧急状态下保障了行为可预测性。

CMS回收失败后触发Serial Old降级,不是配置失误或临时应急,而是HotSpot JVM硬编码的确定性行为——它不选Parallel Old,也不走G1后备路径,直接启用单线程、标记-整理的Serial Old,只为在碎片严重、空间告急时“强行清障”。
为什么一定是Serial Old,而不是并行收集器?
这不是权衡取舍,而是设计锁定:
- CMS与Parallel Old在内存管理模型、并发控制逻辑上不兼容,JVM从未将Parallel Old移植为CMS的Full GC后备方案
- Serial Old采用标记-整理算法,能压缩老年代碎片,腾出连续空间供大对象分配;而CMS的标记-清除无法解决碎片问题,退化时必须靠整理来恢复基本分配能力
- 它是单线程、STW、行为完全可预测的兜底方案——在CMS已失控的紧急状态下,确定性比吞吐量更重要
- JDK 8及之前版本中,CMS + Serial Old是官方唯一支持且经过充分验证的降级组合,稳定性优先于性能优化
什么情况下会真正触发这个降级?
不是CMS“没跑完”,而是它已经撑不住了。关键信号包括:
- GC日志出现Full GC (Concurrent Mode Failure)或Full GC (promotion failed),而非CMS-concurrent-mark等并发阶段记录
- real耗时远大于user+sys(如real=2.8s,user=0.15s),说明长时间STW,无并行参与
- 老年代回收前后几乎无变化(如40960K → 40959K),表明大量对象长期存活,CMS无法有效释放空间
- 日志中只有[Tenured: ]或[SerialOld: ]标识,缺失[CMS]或多线程线程数信息
背后真正的根因在哪?
退化是结果,不是起点。需聚焦三类失衡:
- 老年代持续高水位:jstat -gcutil观察OU指标,长期>85%即危险,>92%(JDK 8默认阈值)极易触发并发启动但随后失败
- 大对象或长生命周期对象堆积:jmap -histo:live重点关注ConcurrentHashMap、byte[]、char[]、自定义缓存类——线上常见WebCache占老年代70%+
- 晋升异常:开启-XX:+PrintTenuringDistribution,若某次Minor GC后晋升量突增(如2MB→20MB),而老年代剩余空间不足,JVM会跳过CMS直接Full GC
现场怎么快速确认和定位?
无需重启,立刻行动:
- 用arthas执行
heapdump --live /tmp/heap.hprof <pid>导出存活堆快照,避免冗余干扰 - 用MAT打开,先看Leak Suspects Report,自动标出retained heap占比最高的可疑对象树
- 若未发现泄漏,切换到Dominator Tree,按Retained Heap排序,重点展开持有大量byte[]或缓存容器的对象链
- 结合代码检查这些对象是否被静态引用、有无淘汰策略(如LRU/TTL)、创建位置是否合理


















