java堆内存通过分代收集策略按对象存活次数(年龄)管理:新对象入eden区,minor gc后幸存者移至survivor区并年龄+1,达阈值(默认15)或survivor空间不足时晋升老年代;老年代不更新年龄。

Java 的堆内存本身并不直接按“年龄”存储对象,而是通过 分代收集(Generational Collection) 策略,根据对象的 存活时间(即“年龄”) 将其分配到不同区域,并动态管理。这里的“年龄”是 JVM 内部维护的一个计数器(Age Counter),记录对象在 Minor GC 中幸存的次数,不是真实年龄或创建时间。
年轻代:绝大多数新对象的起点
新创建的对象默认分配在年轻代的 Eden 区。JVM 采用复制算法进行 Minor GC,Eden 区中存活的对象会被复制到 Survivor 区(From 或 To),每经历一次 Minor GC 且仍存活,其年龄 +1。
- 初始年龄为 0(刚分配时)
- 每次 Minor GC 后仍存活,年龄加 1
- 默认最大年龄阈值为 15(可通过 -XX:MaxTenuringThreshold 调整)
- 年龄达到阈值(如 15)的对象,在下次 Minor GC 时晋升至老年代
Survivor 区:对象“成长”的中转站
Survivor 区(S0 和 S1)用于暂存从 Eden 幸存下来的对象。它不按年龄分区,但对象在此累积年龄。JVM 会根据当前年龄分布和空间压力,动态决定是否提前晋升。
- 并非所有对象都必须活到 MaxTenuringThreshold 才晋升
- 若 Survivor 空间不足,JVM 可能将部分较年轻对象直接“担保晋升”到老年代(Promotion)
- 可通过 -XX:+PrintGCDetails 观察每次 GC 后各年龄对象的大小分布(如 age 1: 123456 bytes)
老年代:长期存活对象的归宿
对象晋升到老年代后,不再参与年轻代 GC,只在 Full GC 或 G1/GC 混合回收阶段被处理。老年代中对象的“年龄”不再更新——因为已不再计算年龄,仅保留其生命周期长度的事实。
- 大对象(如长数组)可能直接分配到老年代(取决于 -XX:PretenureSizeThreshold 和 GC 算法)
- 动态年龄判定:JVM 会统计 Survivor 区中相同年龄对象总大小,若超过 Survivor 半区容量,则把 ≥ 该年龄的所有对象批量晋升(即使未达阈值)
- 老年代本身不维护年龄字段,但可通过堆转储(heap dump)结合对象创建时间估算其“实际存活时长”
如何查看实际年龄分布?
启用详细 GC 日志后,Minor GC 输出中会显示各年龄对象的字节数,例如:
Desired survivor size 524288 bytes, new threshold 7 (max 15)age 1: 1048576 bytes, age 2: 524288 bytes, age 3: 262144 bytes
这表示当前 Survivor 区中,1 岁对象共约 1MB,2 岁约 512KB,3 岁约 256KB。这些数据反映的是本次 GC 后各年龄段对象的累计大小,是调优的重要依据。











