← 每日面试题 学习看板

Java的堆是如何分代的?为什么分代?

面试题/00java/03JVM/05分代与GC触发条件/Java的堆是如何分代的?为什么分代?.md

面试记录 2026-09-21

题目:Java的堆是如何分代的?为什么分代?

难度:medium

标签:jvm/gc, general/knowledge, difficulty/medium

评分:⭐⭐⭐☆☆(3/5)

我的回答

Java 堆的分代,其核心问题是要解决对象存活时间不同的问题。所以它主要划分的方式是年轻代跟老年代。年轻代就是刚建立的对象存储的空间,老年代就是经历了一段时间还存活下来或者比较大的对象存储的空间。这些比较大的对象存储空间存储到老年代的原因是因为年轻代的空间有限。然后为了减少反复的它的反复存活,多次服用复制,可以直接升到老年代。为什么要分代?核心原因其实是对不同类型的对象管理方式的不同。年轻代的对象是朝生夕死,快速生快速死。快速的回收就释放了空间,这样可以更好的管理,更好的释放空间。而老年代存储的是一些,这个对象存活时间比较长了,然后把它在老年代中去存放,让它更稳定一些。只有当 Full GC 的时候才会去动这些老年代的对象,而不是经常性的通过 Young GC 反复的去对它进行操作,降低操作的时间,率。

点评

优点:

不足:

补充:

  1. 年龄晋升:GC 次数达 MaxTenuringThreshold(默认 15)
  2. 动态年龄判定:Survivor 中同龄对象总和 > Survivor 空间 50%,该年龄及更大直接晋升
  3. 大对象晋升:超过 PretenureSizeThreshold 直接在老年代分配
  4. 空间分配担保:Survivor 装不下就把对象搬到老年代

完整讲解

第一部分:知识重点剖析

核心考点定位:考察分代机制的结构性理解(Eden/Survivor/Old 具体划分)+ 分代设计的动机(弱分代假说)+ 晋升机制细节。

知识点拆解:

  1. 堆的完整结构(HotSpot 默认)
   Heap
   ├── Young Generation (约 1/3)
   │   ├── Eden (约 80%)         # 新对象首次分配在这里
   │   ├── From Survivor (约 10%) # 上一次存活对象
   │   └── To Survivor (约 10%)   # 本次存活对象目标
   └── Old Generation (约 2/3)    # 长寿对象 + 大对象
  1. 为什么分代?——弱分代假说
  1. 对象晋升的 4 条规则(HotSpot 实际实现)
  1. 两类 GC 的边界
  1. 分代的代价:跨代引用

第二部分:考察方向与延伸递进

考察方向:

延伸递进面试题:

  1. 为什么 Survivor 只有 10% 空间?
  1. MaxTenuringThreshold 是动态的吗?
  1. G1 的 Region 和传统分代有什么区别?
  1. 跨代引用怎么处理?
  1. JDK 21+ 的分代 ZGC 是什么变化?

第三部分:面试者参考回答(300-600 字口语化)

面试官您好,Java 堆的分代核心是为了不同对象用不同算法,这是 GC 设计的核心权衡。

先说为什么分代,核心是弱分代假说——几乎所有对象朝生夕死,熬过几次 GC 的对象更难死。如果堆不分代:用复制算法浪费空间且大对象无法处理,用标记-整理每次 FullGC 都要移动大部分对象停顿爆炸。所以分代本质是用空间复杂性换算法最优化的可能性。

再说如何分代,HotSpot 堆分为新生代和老年代两部分。

新生代占约 1/3,内部又分 Eden、From Survivor、To Survivor 三区,比例是 Eden:From:To = 8:1:1。新对象默认先在 Eden 分配,Eden 满触发 Young GC,存活对象复制到 To Survivor,Survivor 之间来回交替(Eden:80%、两个 Survivor 各 10%——Survivor 只留 10% 是为了逼早熟对象晋升,避免"熬到老")。

老年代占约 2/3,存长寿对象和大对象。JDK 8 之后还有独立的 Metaspace(不在堆上)。

对象晋升老年代的 4 条规则:

  1. 年龄晋升:GC 次数达 MaxTenuringThreshold(默认 15,动态计算)
  2. 动态年龄判定:Survivor 中同龄对象总和 > 50%,该年龄及更大全部晋升
  3. 大对象晋升:超 PretenureSizeThreshold 直接在老年代分配,避免 Eden 复制开销
  4. 空间分配担保:Survivor 装不下就搬到老年代

两类 GC 的边界:Young GC 只回收新生代;Mixed GC(G1 特有)是 Young GC + 部分老年代 Region(按"脏度"排序);Full GC 是整代回收,是最后手段。所以说"老年代只有 Full GC 才动"是不准确的——G1 的 Mixed GC 也会回收部分老年代 Region。

分代的代价是跨代引用问题:老年代对象引用新生代对象时,Full GC 若全堆遍历会 O(n) 爆炸。所以引入了 Remembered Set(G1)或 Card Table(CMS/Parallel)+ 写屏障,让 GC 只处理"脏"的引用,避免全堆扫描。

最后补充一点演进:ZGC 早期是不分代的(全堆统一),JDK 21 引入分代 ZGC,进一步降低停顿、提高吞吐。