在 JVM 的分代垃圾回收机制中,对象会在 Survivor 区(S0/S1)之间反复复制。每经过一次 Minor GC 且存活,对象的年龄(Age)就会加 1。当年龄达到阈值(默认 15)时,对象会被晋升到老年代。
面试官经常问:“为什么默认是 15?我能把它改成 25 吗?” 这道题考察的是对 Java 对象内存布局 的底层理解。
1. 核心概念
JVM 中对象的内存布局主要包含三部分:
- 对象头(Object Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
其中,对象头包含两部分信息:
- Mark Word:存储哈希码、GC 分代年龄、锁状态标志等。
- Klass Pointer:指向类元数据的指针。
问题的答案就隐藏在 Mark Word 的结构中。
2. 原理与源码分析
2.1 Mark Word 的位定义
在 64 位虚拟机(开启压缩指针)中,Mark Word 占用 8 字节(64 bit)。虽然不同锁状态下 Mark Word 的布局不同,但在无锁状态下,其结构大致如下:
| Bit Range | Meaning |
|---|---|
| 25 bit | unused |
| 31 bit | hashCode |
| 1 bit | unused |
| 4 bit | GC Age |
| 1 bit | biased_lock |
| 2 bit | lock |
关键点来了: JVM 在 Mark Word 中仅分配了 4 个 bit 来存储对象的 GC 年龄。
2.2 二进制计算
根据二进制计算:
- 4 bit 能表示的最大无符号整数是
1111(二进制)。 - 转换成十进制就是
8 + 4 + 2 + 1 = 15。
因此,对象的分代年龄最大只能存储到 15。这是由底层存储结构决定的硬性限制。
3. 尝试设置为 25 会发生什么?
我们可以通过 JVM 参数 -XX:MaxTenuringThreshold 来调整这个阈值。
如果你在启动 JVM 时尝试设置:
java -XX:MaxTenuringThreshold=25 -jar app.jar
结果是:JVM 启动失败,并报错。
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
MaxTenuringThreshold of 25 is invalid; must be between 0 and 15
JVM 在启动参数校验时,会明确检查该值是否在 0 到 15 之间。
4. 动态年龄判定(补充考点)
虽然最大年龄是 15,但对象不一定非要达到 15 岁才会晋升老年代。JVM 还有动态对象年龄判定机制:
如果在 Survivor 空间中,相同年龄所有对象的大小总和大于 Survivor 空间的一半(TargetSurvivorRatio,默认 50%),那么年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold。
5. 总结
面试回答总结:
JVM 分代年龄限制为 15,根本原因是对象头(Object Header)的 Mark Word 结构设计。 在 Mark Word 中,只预留了 4 个 bit 的空间来存储 GC 年龄。因为 4 个 bit 能表示的最大数值是
1111(即 15),所以最大年龄无法超过 15。 如果尝试通过-XX:MaxTenuringThreshold设置为大于 15 的值(如 25),JVM 会在启动时校验失败并报错。 此外,实际晋升还受动态年龄判定规则影响,不一定非要达到最大阈值。