PromotionFailed 表明年轻代对象晋升老年代失败,主因是老年代缺乏足够连续空间,需检查 SurvivorRatio、MaxTenuringThreshold 及内存碎片;G1 下对应 to-space exhausted,应分析 G1HeapRegionSize 与混合回收策略。

看到 PromotionFailed 就该立刻怀疑年轻代空间分配策略
PromotionFailed 不是孤立事件,它意味着 CMS 或 G1 在 Minor GC 后尝试把存活对象从 Young Gen 晋升到 Old Gen 时失败——老年代没足够连续空间容纳这批晋升对象。此时 JVM 强制触发 Full GC,但 Full GC 往往也清理不出足够空间(尤其存在大量长期存活对象或内存泄漏),形成恶性循环。
关键不是“为什么 Full GC 发生了”,而是“为什么这批对象非得现在晋升”。常见诱因包括:
-
SurvivorRatio设置过大(如-XX:SurvivorRatio=16),导致S0/S1过小,对象躲不过一次 Minor GC 就直接进入tenured -
MaxTenuringThreshold被显式设为0或极低值(如1),强制对象在第一次 GC 后就晋升 - 应用突发大量中生命周期对象(如批量导入、报表导出),
Eden区虽未满,但Survivor已无法容纳转移对象,触发premature tenuring
用 jstat -gc 验证晋升行为是否异常
光看日志不够,要确认实际晋升量是否远超预期。在 PromotionFailed 出现前后执行:
jstat -gc <pid> 1000 5
重点关注这几列:
-
S0C/S1C:Survivor 容量是否持续接近 0?说明 Survivor 区根本存不住对象 -
EC:Eden使用率是否长期低于 70% 却频繁 GC?暗示对象存活率高、快速溢出 -
YGC和YGCT是否陡增?配合FGC上升,说明 Minor GC 失去缓冲作用 -
EU(Eden 使用量)和S0U/S1U的比值是否稳定 —— 若每次 GC 后S0U ≈ 0且OU(Old Used)明显跳涨,基本可判定对象全量晋升
-XX:+PrintGCDetails 日志里找真实晋升大小与阈值对比
开启 GC 日志后,典型 PromotionFailed 前的 Minor GC 行会类似:
[GC (Allocation Failure) [PSYoungGen: 892345K->32456K(917504K)] 1234567K->1123456K(2097152K), 0.1234567 secs]
注意这里隐含两个关键数字:
AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。
- 晋升量 ≈
1234567K - 892345K = 342222K(即老年代使用量增长) - 而当前
Old Gen剩余空间 =2097152K - 1123456K = 973696K,看似够用——但PromotionFailed仍发生,说明 JVM 判断的是「连续空闲空间」不足,而非总量
这意味着老年代存在严重碎片。此时必须结合 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 查看紧邻的 Full GC 日志,确认是否出现 concurrent mode failure(CMS)或 to-space exhausted(G1),它们才是碎片化的直接证据。
G1 下的 PromotionFailed 实际对应 to-space exhausted,别被名字误导
G1 日志里几乎不出现字面的 PromotionFailed,取而代之的是:
To-space exhausted Evacuation failed
这本质是同一类问题:G1 在 Evacuation 阶段要把 Region 中存活对象复制到 to-space(相当于其他 GC 的 Survivor 或 Old),但目标 Region 不足或跨代复制时老年代没有可用连续 Region。
排查重点转向:
-
-XX:G1HeapRegionSize是否过大(如4M),导致小对象无法填充、加剧内部碎片 -
-XX:G1MixedGCCountTarget是否太低(如2),使混合回收频率不足,老年代垃圾堆积 - JVM 启动时是否漏加
-XX:+UseStringDeduplication?字符串重复引用是 G1 下老年代膨胀的隐形推手
真正棘手的 case 往往不是参数调错,而是业务代码在短生命周期内反复创建大数组或缓存对象,又没及时释放引用——PromotionFailed 只是表象,背后大概率藏着未关闭的流、静态 Map 持有 ResponseBody、或线程局部变量未清理。日志和工具只能定位“哪里破了”,修复还得回到堆转储(jmap -dump)里看谁占着老年代不撒手。

















