对象晋升老年代发生在Minor GC过程中,由JVM实时依据年龄阈值、Survivor空间不足或大对象直入等条件动态判定,而非按时间自动迁移。

对象进入老年代不是“什么时候”,而是“在某次 Minor GC 扫描过程中,因空间或年龄条件被 JVM 实时判定晋升”。没有固定时间点,只有触发条件。
对象在 Minor GC 时被动态判定晋升
晋升动作只发生在 Minor GC 过程中,不是对象创建后按秒/分钟计时自动搬移。JVM 在每次 Minor GC 扫描存活对象时,才检查是否满足晋升条件。关键点:
- 对象年龄只在 Minor GC 后+1,且仅记录在对象头中,不随时间增长
- 即使对象已存活 10 分钟但从未触发 GC,年龄仍为 0,不会晋升
- GC 日志里的
new threshold 2 (max 15)表示本次动态算出的晋升起点年龄,下一轮 GC 才生效
Survivor 区空间不足导致强制晋升
这是最易被忽略却高频发生的晋升原因——尤其在流量突增、批量导入等场景。当 To Survivor 区装不下所有存活对象时:
- 不管年龄是 1 还是 0,超出部分直接进老年代(分配担保)
- 常见诱因:
-XX:SurvivorRatio=2(每个 Survivor 仅占年轻代 1/4),或-Xmn设得过小 - 可通过
-XX:+PrintTenuringDistribution观察每轮 GC 后各年龄对象大小分布,确认是否频繁出现age 1: xxx bytes, xxx total超过 Survivor 一半
大对象直入老年代的生效边界
-XX:PretenureSizeThreshold 只对 Serial 和 ParNew 收集器有效,在 G1、ZGC、Shenandoah 中被忽略。使用时注意:
- 阈值单位是字节,设
-XX:PretenureSizeThreshold=4194304表示 ≥4MB 的对象跳过新生代 - 数组和连续字节数组(如
new byte[4_200_000])最典型;普通 POJO 即使字段多,只要总大小未超阈值,仍走 Eden 流程 - 该参数与
-XX:+UseAdaptiveSizePolicy冲突:后者启用时会覆盖预设阈值,建议显式关闭自适应策略再使用
老年代空间不足会阻断 Minor GC
JVM 不会在老年代快满时“悄悄晋升”,而是提前拦截:
- 每次 Minor GC 前,先预判:若 Survivor 装不下,老年代是否有足够连续空间容纳这批待晋升对象?
- 判断依据是历史晋升均值(
-XX:TargetSurvivorRatio默认 50% 也参与此计算) - 若预判失败,直接触发 Full GC;若 Full GC 后仍不够,抛出
java.lang.OutOfMemoryError: Java heap space
真正难调的是动态年龄判定与 Survivor 容量的耦合关系:一个参数调小可能让 age1 对象全跑进老年代,而调大又可能导致 Survivor 频繁溢出——它从来不是独立变量,而是 GC 压力在内存布局上的实时映射。


















