动态年龄判定是jvm在minor gc后按年龄从小到大累加survivor区对象大小,首次累计≥survivor容量×targetsurvivorratio(默认50%)时,该年龄及更老对象即晋升老年代;其阈值每轮gc可变,由实际分布决定,不固定依赖maxtenuringthreshold。

动态年龄判定不是看对象“活够几轮”,而是看 Survivor 区里某一年龄段的对象是否快把空间占满。只要从 age=1 开始逐档累加,某次累加后总大小首次 ≥ Survivor 容量 × TargetSurvivorRatio(默认 50%),那这一档及更老的对象,下一次 Minor GC 就全部晋升到老年代。
晋升阈值由分布决定,不是固定数字
实际晋升年龄每轮 GC 都可能变,关键看 Survivor 区中各年龄对象的大小分布:
- age1 占 42%,age2 占 15% → 累计到 age2 达 57% > 50% → 新阈值为 2,age2 及以上对象下次 GC 晋升
- age1 占 30%,age2 占 20%,age3 占 8% → 累计到 age3 达 58% → 新阈值为 3
- 所有年龄累计 ≤ 50% → 回退到按 MaxTenuringThreshold(如 15)晋升
GC 日志里找真实晋升年龄
开启 -XX:+PrintGCDetails 后,Minor GC 日志中会出现类似这行:
Desired survivor size 524288 bytes, new threshold 3 (max 6)
其中 new threshold 3 就是本次动态算出的晋升起点年龄。它不等于 max 值,也不稳定:
- 长期显示 new threshold 6 (max 6):Survivor 空间宽松,动态机制未触发
- 频繁出现 new threshold 1 或 2:Survivor 过小,或某类对象生命周期高度一致(如批量请求生成的 DTO)
- age 1: 262144 bytes, 262144 total —— 若 Survivor 是 512KB,age1 已超半,直接触发 age1 晋升
影响判定的关键配置参数
这个机制依赖几个联动参数,单改一个往往无效:
- -XX:SurvivorRatio:控制 Eden 与一个 Survivor 的比例。设为 2(即 Eden:S0:S1 = 2:1:1)会让每个 Survivor 仅占年轻代 1/4,极易触发 age1 晋升;推荐用 6~8(每个 Survivor 占 1/8~1/10)留出缓冲余量
- -XX:TargetSurvivorRatio:默认 50%,是动态判定的开关阈值。仅在 -XX:+UseAdaptiveSizePolicy 启用时生效(该参数默认开,但若被显式关闭,整套机制失效)
- -XX:MaxTenuringThreshold:只是上限,不参与日常计算。即使设为 15,只要 age2 对象占满 Survivor 一半,JVM 就会强制 age2+ 晋升
为什么容易误判“对象该不该老”
年龄只是历史记录,真正晋升发生在 Minor GC 扫描时。它本质是空间保底策略,不是生命周期判断:
- 目的不是“让老对象搬家”,而是防止 Survivor 被大龄对象卡死,导致后续小对象无处可放
- 大量 age=1 对象“一岁进老年代”,通常不是代码问题,而是 Survivor 太小或流量突发导致存活对象集中
- 调低 MaxTenuringThreshold(如设成 3)未必省事——短期存活对象提前进老年代,反而抬高老年代使用率,引发更频繁 Full GC










