动态年龄判定的触发条件是:每次Minor GC时,若某一年龄及更老对象总大小超过Survivor区当前可用容量的50%,则立即晋升;该阈值由JVM硬编码为MAX_SURVIVOR_SPACE_LIMIT,不可配置。

直接看 -XX:+PrintTenuringDistribution 输出的 age 表,就能看清对象在 Survivor 区里“活了几轮”、哪批被推走、为什么提前走——它比 -XX:+PrintGCDetails 更细粒度地暴露晋升的真实逻辑。
关键字段怎么看
每次 Minor GC 后日志中会出现类似内容:
Desired survivor size 524288 bytes, new threshold 3 (max 15)
- age 1: 262144 bytes, 262144 total
- age 2: 196608 bytes, 458752 total
- age 3: 131072 bytes, 589824 total
-
Desired survivor size:JVM 计算出的 Survivor 区“理想占用上限”,等于当前 Survivor 容量 ×
TargetSurvivorRatio(默认 50%) -
new threshold:本轮实际生效的晋升年龄门槛,不是你设的
MaxTenuringThreshold,而是 JVM 动态算出来的 - age X: Y bytes:所有恰好经历过 X 次 Minor GC 还存活的对象总大小
-
total:从 age 1 累加到当前 age 的所有对象总大小;一旦某行
total ≥ Desired survivor size,该 age 就是新阈值
如何识别真实晋升行为
晋升不是等对象“活满 15 轮”,而是看空间压力。重点观察三类信号:
- 如果
age 1: 400KB, total 400KB,而Desired survivor size = 512KB→total已接近上限,但未超,不会晋升;若下一轮变成age 1: 480KB,就直接触发new threshold 1 - 若看到
new threshold 2且age 2: 300KB, total 650KB(远超 Desired 值),说明 age 2 及更老对象已占满 Survivor 半壁江山,JVM 强制它们全部进老年代 - 若
new threshold 0频繁出现,意味着 Survivor 完全塞不下,连刚分配的对象都绕过复制,直送老年代——这是严重过早晋升信号
结合堆变化反推晋升量
单靠 age 表看不出“送走了多少”,需搭配 -XX:+PrintGCDetails 中的内存变化:
- 找到日志中类似:
[PSYoungGen: 71680K->5120K(71680K)] 112800K->81912K(159232K) - 年轻代减少量 = 71680K − 5120K = 66560K
- 整个堆减少量 = 112800K − 81912K = 30888K
- 晋升量 = 66560K − 30888K = 35672K ≈ 35MB
- 再回看 age 表中
total值(比如 589824 字节 ≈ 0.56MB),远小于 35MB → 说明这批晋升对象绝大多数根本没进过 Survivor,是大对象或担保失败导致
怎么判断是不是参数配错了
不用猜,用连续多轮 age 表走势说话:
- 如果
age 1总是占满 Desired 值甚至翻倍 → Survivor 太小,该调小SurvivorRatio或加大新生代 - 如果
age 2突增明显(如 800KB),age 3断崖下跌(如 50KB)→ 大量对象活过 2 轮就死,把MaxTenuringThreshold设为 2 更合理 - 如果各 age 分布均匀、
new threshold在 4~7 之间小幅波动 → 当前配置已较稳定,不建议强行干预


















