对象进入老年代有四种情形:1.年龄达-XX:MaxTenuringThreshold阈值(默认15);2.动态年龄判断——Survivor中某年龄及以上对象总大小超其空间50%,则该年龄起全部晋升;3.大对象超-XX:PretenureSizeThreshold直接分配至老年代;4.Survivor空间不足或担保失败时提前晋升。

对象进入老年代,不单看“活了几次”,关键要看 Survivor 区里对象的年龄分布和空间占用情况。JVM 的 Object Aging 机制本质是两套规则协同工作:一套是按年龄计数的静态上限(-XX:MaxTenuringThreshold),另一套是基于 Survivor 实际使用状况的动态判定——后者才是真正影响日常晋升行为的主力。
对象年龄怎么算出来的
每次 Minor GC 后,Eden 和 From Survivor 中存活的对象被复制到 To Survivor,同时年龄 +1。刚从 Eden 复制过来的对象年龄为 1,再活过一次 GC 就变成 2,依此类推。这个年龄记录在对象头中,是 JVM 管理对象生命周期的基础信息。
注意:年龄不是“时间”或“秒数”,而是“躲过的 Minor GC 次数”。哪怕两次 GC 间隔只有几毫秒,只要发生了,年龄就加 1。
动态年龄判定才是日常晋升的关键
JVM 不会死守 MaxTenuringThreshold(默认 15)。它每轮 Minor GC 都会扫描当前 To Survivor 区,按年龄从小到大累加各档对象的大小:
- 先加所有年龄 1 的对象大小
- 再加所有年龄 2 的对象大小
- 继续加年龄 3、4……直到累计占比 ≥ Survivor 总容量的 50%
一旦达到这个临界点,所有年龄 ≥ 当前档位的对象,本轮 GC 后直接晋升老年代。例如:年龄 1 占 35%,年龄 2 占 22%,累计 57% → new threshold = 2 → 所有年龄 ≥2 的对象全部进老年代。
这个实际生效的阈值会在 GC 日志中明确打出:new threshold 2 (max 6)。它每轮都可能变,不是配置值,而是运行时计算结果。
SurvivorRatio 和 TargetSurvivorRatio 如何影响晋升行为
-XX:SurvivorRatio 控制 Eden 与一个 Survivor 的比例(如设为 6,表示 Eden:S0:S1 = 6:1:1,每个 Survivor 占年轻代 1/8)。这个值太小(比如设成 2),会导致 Survivor 空间紧张,轻微波动就触发动态晋升,甚至让大量 age=1 对象直接进老年代。
-XX:TargetSurvivorRatio(默认 50%)是动态判定的开关阈值,但它只在 -XX:+UseAdaptiveSizePolicy 开启时起作用(该参数默认启用,但若被显式关闭,整套动态机制就失效)。
实践中更稳的配置是:SurvivorRatio=6~8,给 Survivor 留出缓冲余量;避免把 TargetSurvivorRatio 调低(如设成 30%),否则会让晋升阈值频繁下探,加重老年代压力。
什么情况下对象会绕过年龄判断直接进老年代
除了动态年龄判定,还有三种常见绕过路径:
- Survivor 空间不足:Minor GC 后存活对象总大小 > To Survivor 剩余空间 → 直接进老年代(不管年龄)
-
大对象直入老年代:对象大小超过
-XX:PretenureSizeThreshold(如设为 1MB),创建时就分配到老年代 - 老年代担保失败触发:Minor GC 前发现老年代剩余空间
这些情况在高并发、批量请求或突发流量场景中极易出现,不能只盯着 age 阈值看。

















