新生代晋升失败指minor gc后老年代无足够连续空间容纳存活对象,触发full gc或oom;主因是老年代真空间不足或碎片化,cms易因标记-清除产生碎片,g1通过region机制缓解。

新生代晋升失败(Promotion Failure)是 Java GC 中一种严重的内存问题,通常发生在 Minor GC 后,JVM 尝试将存活对象从 Eden 或 Survivor 区复制到老年代时,发现老年代没有足够连续空间容纳这些对象,且无法触发 Full GC(例如因为 CMS 正在并发标记、G1 未启动 Mixed GC、ZGC/Shenandoah 不支持同步晋升等),导致 Minor GC 失败并抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded 或直接 java.lang.OutOfMemoryError: Metaspace(间接相关),更常见的是触发一次代价高昂的 Full GC 或直接崩溃。
看日志中是否出现 Promotion Failure 关键字或对应行为
不同垃圾收集器的日志格式差异较大,需结合 GC 类型识别:
-
G1:查找类似
to-space exhausted、Evacuation failed、Promotion failed的提示;日志中若出现Full GC (Ergonomics)紧跟在 Minor GC 后,大概率是因晋升失败触发的兜底 Full GC。 -
CMS:CMS 本身不处理 Minor GC 晋升,但当老年代碎片化严重,Minor GC 需晋升时发现无法分配(尤其是大对象直接分配失败),会报
concurrent mode failure—— 这本质就是晋升失败的后果。日志中可见concurrent mode failure+ 紧接着Full GC。 -
Parallel / Serial:日志中明确出现
Promotion Failed(旧版 HotSpot)或Desired survivor size远大于实际可用 Survivor 空间,同时老年代capacity足够但used接近capacity,且max tenuring threshold已到但对象仍无法晋升,说明老年代碎片或已满。
重点检查老年代剩余空间与碎片化程度
晋升失败的直接原因是老年代无法容纳待晋升对象,但原因分两种:
-
真·空间不足:老年代
used接近capacity(如 >95%),且没有可回收对象(比如大量长生命周期缓存、静态集合未清理)。此时需分析堆直方图(jmap -histo)或用 JFR / VisualVM 查看大对象分布。 -
空间充足但碎片化:尤其 CMS 和 Serial Old 容易出现。日志中老年代
used可能仅 70%,但最大连续空闲块(largest free chunk)远小于待晋升对象大小。CMS 日志中若有coordinator相关警告或precleaning失败,常伴随碎片加剧。
排查 Survivor 区配置与对象年龄异常
如果对象本该在 Survivor 区“熬过”几次 GC 后再晋升,却因 Survivor 空间太小被迫提前进入老年代,会加速老年代填满:
- 检查
-XX:SurvivorRatio(如设为 8 表示 Eden:Survivor=8:1:1),过小的 Survivor 会导致Desired survivor size不足,大量对象在tenuring threshold=1就晋升。 - 用
-XX:+PrintTenuringDistribution查看每次 GC 后各年龄对象大小。若某次 Minor GC 后,年龄 1 的对象占比突增且总量巨大(如 200MB),说明新对象“秒进老年代”,可能 Survivor 不够或对象太大无法放入 Survivor。 - 注意大对象(≥
-XX:PretenureSizeThreshold)直接分配在老年代,绕过新生代 —— 这类对象虽不参与晋升,但会快速耗尽老年代空间,间接导致后续正常晋升失败。
结合 GC 参数与运行时行为交叉验证
单看日志不够,要关联 JVM 启动参数和应用行为:
- 是否禁用了自适应策略?如加了
-XX:-UseAdaptiveSizePolicy,可能导致 Survivor 大小固定、tenuring threshold 锁死,无法动态应对流量变化。 - 是否使用 CMS 但未配
-XX:CMSInitiatingOccupancyFraction?默认值过高(约 92%),老年代快满了才启动并发回收,容易来不及清理就遭遇晋升失败。 - 应用是否存在突发性对象创建高峰(如批量导入、报表导出)?这类场景下 Eden 快速填满 → Minor GC 频繁 → 大量对象达到阈值 → 集中晋升 → 老年代瞬间撑爆。
定位后,常见解决方向包括:调大老年代(-Xmx 与 -Xms 一致避免扩容抖动)、调优 SurvivorRatio/MaxTenuringThreshold、启用 G1/ZGC 减少碎片、清理无用静态引用、控制大对象分配、增加 CMS 触发阈值或改用 G1。不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











