老年代担保机制担保的是Minor GC前老年代最大连续空闲空间能否容纳本次晋升对象:先比对是否≥新生代全部对象总大小,不满足则再比对是否≥历史平均晋升大小,任一不通过即触发Full GC。

老年代担保机制到底在担保什么
它不是事后补救,而是 Minor GC 启动前的一次强制预判:老年代有没有一块足够大的连续空闲区域,能接住本次 GC 后可能晋升的所有对象。核心目标是避免“晋升失败”——即 Survivor 区装不下存活对象、又无法安全转移到老年代,最终触发 Full GC 甚至线程卡死。
担保判断分两步,缺一不可
JVM 不看老年代剩余多少总空间,只看最大一块连续空闲区域(这是关键!碎片再多也没用):
- 第一层安全线:老年代最大连续空闲空间 ≥ 新生代当前所有对象总大小(Eden + Survivor 中待处理对象)。满足则直接放行 Minor GC,100% 安全
- 第二层冒险线:不满足第一层时,检查该连续空间是否 ≥ 历史平均晋升大小(JVM 自动统计,存为 _avg_promoted_size)。满足则“赌一把”,允许 Minor GC 继续,但有失败风险;不满足,立刻触发 Full GC
哪些场景会让担保失效
担保失败往往不是因为老年代太小,而是结构或行为出了问题:
- 老年代碎片严重:用了 60% 空间,但剩下全是 512KB 小块,而一个晋升对象要 2MB —— 连续空间不足
- 突增式晋升:某次请求批量创建大量中生命周期对象,远超历史平均值,导致“冒险担保”失准
- Survivor 区过早清空:-XX:TargetSurvivorRatio 默认仅 50%,稍有波动就提前把对象往老年代推,放大晋升压力
- 大对象绕过 Survivor 却没设阈值:比如 JSON 解析生成的 3MB byte[],若未配 -XX:PretenureSizeThreshold,会先在 Eden 分配、复制、再晋升,白白消耗担保资源
怎么让担保更稳、更可预期
调参重点不是堆大小,而是控制晋升节奏和减少不确定性:
- 把 -XX:TargetSurvivorRatio 调到 90,让 JVM 更充分地利用 Survivor,减少“本可留下却提前走”的无效晋升
- 对已知长生命周期或大体积对象(如缓存、报表数据),用 -XX:PretenureSizeThreshold 直接分配到老年代,跳过年轻代流程
- 观察 GC 日志里 “promoted” 字样与老年代 used 增长是否匹配:如果 promoted 很小但 old used 暴涨,说明大量对象因 Survivor 满而被迫晋升,需检查对象年龄分布
- JDK 8u40+ 已移除 -XX:+HandlePromotionFailure 开关,它现在是硬编码逻辑,禁用参数无效,也不建议尝试绕过


















