对象老化由在新生代中跨Survivor区复制次数决定:Eden→Survivor年龄=1,每跨一次+1;年龄存于对象头,最大15;达阈值或动态判定(如age≥3对象超Survivor一半)即晋升老年代。

对象老化不是按时间推算,而是由它在新生代中“被复制到 Survivor 区”的次数决定。每次成功跨 Survivor 区转移,年龄就 +1;没进过 Survivor,年龄就是 0。
Eden → Survivor:第一次存活即年龄 = 1
新对象总是在 Eden 区分配。当 Eden 满触发 Minor GC,仍存活的对象会被复制到当前空的 Survivor 区(比如 S0)。这次复制是对象**首次进入 Survivor 区**,JVM 就把它的年龄设为 1。
- 刚创建的对象,即使没经历 GC,年龄也是 0
- 只有从 Eden 复制到 S0 或 S1,才算“熬过第一关”,年龄变为 1
- 如果 Eden 满时,对象还没来得及被 GC 扫描(比如刚 new 出来几毫秒),它依然在 Eden 中,年龄仍是 0
S0 ↔ S1 之间每交换一次,年龄 +1
S0 和 S1 不是固定角色,而是一对可互换的“源区”和“目标区”。某次 GC 中,S0 是 from 区、S1 是 to 区;下一次 GC 就反过来。对象只要从一个 Survivor 区被复制到另一个,年龄就加 1。
- Eden → S0:年龄 = 1
- S0 → S1:年龄 = 2
- S1 → S0:年龄 = 3
- 以此类推,不依赖具体是 S0 还是 S1,只看“是否跨 Survivor 区复制”
晋升老年代不止看年龄阈值
默认 -XX:MaxTenuringThreshold=15,但实际晋升往往更早。JVM 会动态判断:
- 年龄达到阈值:直接晋升
- 动态年龄判定:某次 GC 后,S0 中所有 age=3 的对象总大小超过 S1 容量的一半 → 所有 age ≥ 3 的对象全部进老年代
- 担保失败:Survivor 空间不够装下本轮存活对象 → 多余部分直接晋升,不管年龄
- 大对象直入老年代:若对象大小超过 Eden 的一半(如 -XX:PretenureSizeThreshold 控制),跳过新生代,直接分配到老年代
年龄计数器存在对象头中,不随复制丢失
年龄不是存在 S0/S1 内存里,而是记录在每个 Java 对象的对象头(Object Header)中。所以哪怕对象从 S0 复制到 S1,年龄信息也跟着一起搬过去,不会重置或丢失。
- 对象头中的 age 字段占 4 位(最大支持 15),因此理论最大年龄是 15
- CMS 收集器默认阈值是 6,G1 则完全不使用该参数,改用自适应晋升策略
- 调大 SurvivorRatio(如设成 16)会压缩 S0/S1,容易触发早晋升,反而加重老年代压力










