堆内存分代结构
候选人小李在面试美团 P6 时,面试官问道:
"JVM 堆的分代结构是什么?Minor GC 和 Full GC 有什么区别?"
小李说:"堆里有 Eden、Survivor 和老年代..."面试官追问:"为什么对象要优先在 Eden 区分配?"
小李说:"因为 Eden 区空间大..."面试官继续追问:"对象什么时候会进入老年代?年龄阈值默认是多少?"
小李答不上来了...
一、核心问题:堆内存分代结构 🔴
1.1 问题拆解
1.2 ❌ 错误示范
候选人原话 A:"Eden 区和 Survivor 区的比例是 1:1。"
问题诊断:默认比例是 Eden : Survivor = 8 : 1(即 -XX:SurvivorRatio=8,默认 8)。两个 Survivor 区各占年轻代的 1/10。
候选人原话 B:"所有对象都在堆上分配。"
问题诊断:不一定。经过逃逸分析后,如果对象不逃逸出方法/线程,可以进行栈上分配(Stack Allocation),甚至标量替换(Scalar Replacement),对象直接在栈上分配,无需 GC。
1.3 标准回答
P5 级别:分代结构
JVM 堆的分代:
Y --> E[Eden 区
约 80% 年轻代] Y --> S0[Survivor 0
约 10% 年轻代] Y --> S1[Survivor 1
约 10% 年轻代]
O --> O1[Old Gen
长期存活对象]
P6 级别:对象分配与晋升
对象分配流程:
TLAB(Thread Local Allocation Buffer):
为减少并发分配时的竞争,JVM 为每个线程在 Eden 区预分配一块专属缓冲区:
对象晋升条件:
年龄达到阈值:Minor GC 后,对象的年龄 age 增加 1。当 age
>=-XX:MaxTenuringThreshold(默认 15)时,进入老年代。动态年龄判定:如果 Survivor 区内相同年龄所有对象的大小之和超过 Survivor 空间的 50%,则 age
>=该年龄的对象直接进入老年代。大对象直接进入老年代:超过
-XX:PretenureSizeThreshold(默认 0,不启用)的对象直接在老年代分配。
P7 级别:分代假设与调优
分代假说(Generational Hypothesis):
JVM 的分代设计基于两个假设:
- 弱分代假说:大多数对象都是朝生夕死的(大部分对象很快变得不可达)
- 强分代假说:熬过多次 GC 的对象倾向于继续存活
这两个假设使得分代 GC 策略非常高效——只需要扫描年轻代的小部分存活对象。
分代配置策略:
一个容易被忽视的参数:
-XX:TargetSurvivorRatio(默认 50):Survivor 区使用率超过这个值时,即使未达到年龄阈值,对象也会提前晋升。
【面试官心理】 这道题我能问到 P7 级别,是因为分代结构涉及了 GC 策略的核心假设和调优参数。能说出 TargetSurvivorRatio 的候选人说明他对 GC 调优有实战经验。能解释动态年龄判定的候选人说明他看过相关源码。
1.4 追问升级
追问 1:为什么 Survivor 区要设计成两个(S0 和 S1)?
Survivor 分区的设计是为了解决内存碎片问题。Minor GC 后,存活对象从 Eden + 一个 Survivor 区复制到另一个 Survivor 区(Copying GC 算法)。这样 Eden 和 Survivor 区的内存始终是连续的,避免了标记-清除算法的碎片问题。
S0 和 S1 每次只有一个被使用,另一个空闲。角色在每次 Minor GC 后交换。
追问 2:对象头中年龄占几位?最大年龄为什么是 15?
对象头 Mark Word 中的 GC 分代年龄字段只有 4 位(
age:4),最大表示 15。当 age 达到 15 时,对象晋升到老年代(JDK 8 及之前)。JDK 11+ 引入了 G1,G1 中对象的年龄概念有所变化。
二、生产调优 🟡
2.1 Survivor 空间配置
2.2 大对象配置
三、常见问题 🟡
3.1 对象过早晋升
场景:Minor GC 后,大量对象进入老年代,频繁触发 Full GC。
根因:Survivor 区太小,对象年龄未达到阈值就因 Survivor 区空间不足而提前晋升。
解决:增加 Survivor 区大小(-XX:SurvivorRatio)或增大年轻代比例。
3.2 内存分配担保
Minor GC 前,JVM 检查老年代最大可用连续空间是否大于年轻代所有对象总空间。如果小于,则检查是否允许担保失败。如果允许,则尝试 Minor GC。否则,执行 Full GC。
面试加分点:能说出"JDK 11 的 ZGC 已经不再使用传统的分代结构,它使用着色指针和读屏障技术,实现了几乎无停顿的 GC(停顿时间 < 1ms)",说明他关注了现代 GC 的演进。
面试陷阱:被问到"对象一定在 Eden 区分配吗",很多人会说"是"。准确答案是:不一定。大对象(超过 PretenureSizeThreshold)直接在老年代分配;TLAB 分配在线程专属缓冲区;逃逸分析后可能栈上分配。