← 返回题目列表

对象什么时候会从年轻代进入老年代?

中等 第 17 / 34 题 更新于 2026/07/25
垃圾回收对象晋升

简化版

在 HotSpot 的典型分代收集器中,对象通常先在 Eden 分配,Young GC 存活后进入 Survivor,并随存活次数增加年龄。达到晋升年龄、Survivor 容量不足或收集器判定应提前晋升时,会进入老年代。大对象是否直接进入老年代取决于收集器:PretenureSizeThreshold 只适用于部分收集器,G1 的超大对象则按 Humongous Region 处理,不能把这些规则当成所有 JVM 的统一规定。

详细版

典型晋升路径有三类:

  1. 年龄晋升:对象熬过多次 Young GC,年龄达到本次收集采用的阈值。
  2. 空间压力晋升:Survivor 容纳不下存活对象,部分对象提前晋升;如果老年代也接不住,可能导致晋升失败或更重的 GC。
  3. 收集器特殊分配:部分传统收集器可让超过阈值的大对象直接进入老年代;G1 的 Humongous 对象占用一个或多个连续 Region,规则不同。

-XX:MaxTenuringThreshold 在常见 HotSpot 配置下上限常见为 15,但默认值、实际阈值和是否适用受收集器与 JDK 版本影响。线上判断应看年龄分布和 GC 日志,不应只背“默认 15 次”。

完整版教学

一、对象的典型分代旅程

以带 Eden、两个 Survivor 的传统分代布局为例:

新对象 → Eden
           │ Young GC 后仍存活

       Survivor From ⇄ Survivor To
           │ 年龄阈值或空间压力

         老年代

Young GC 会复制存活对象。对象每经历一次适用的年轻代回收,年龄通常加一;达到本次计算出的晋升阈值后,复制目标就可能从 Survivor 变成老年代。

Eden 与 Survivor 的比例是可调并可能被自适应策略改变,“永远是 8:1:1”不是规范保证。

二、年龄阈值不是固定倒计时

-XX:MaxTenuringThreshold 表示 HotSpot 某些分代收集器允许的最大晋升年龄,但实际晋升年龄可以更小。

收集器会观察 Survivor 中各年龄对象占用,并为了满足目标 Survivor 大小计算本轮阈值。可把决策抽象为:

实际晋升阈值 ≤ MaxTenuringThreshold

假设目标 Survivor 容量为 100 MB,年龄分布为:

年龄本年龄对象1 到该年龄累计
135 MB35 MB
240 MB75 MB
330 MB105 MB

为了不让复制后的 Survivor 超过目标,收集器可能把晋升阈值降到 3 左右,使达到该年龄的对象提前进入老年代。精确算法与日志含义应以对应 JDK/收集器实现为准。

三、Survivor 放不下会怎样

Young GC 后的存活对象必须有去处。当 Survivor 空间不足时,能放下的对象进入 Survivor,其他符合条件的对象会尝试晋升到老年代。

若一次回收有 120 MB 存活对象,而可用 Survivor 只有 60 MB,就至少有约 60 MB 需要另找去处;实际分配还受对象大小、年龄和区域布局影响。

老年代需要具备足够的可用空间承接晋升。接不住时,不同收集器可能触发 Full GC、退化回收或报告分配失败。所谓“空间分配担保”是 HotSpot 分代回收的实现机制,不是 Java 语言规范条款。

晋升的本质不是给对象颁发“年龄证书”,而是在存活对象、复制空间和老年代容量之间做放置决策。

四、大对象不应套用同一条规则

在部分传统 HotSpot 收集器中,-XX:PretenureSizeThreshold 可控制超过阈值的对象直接在老年代分配,但该参数并不对所有收集器生效。

G1 把超过单个 Region 一半的对象视为 Humongous 对象,通常从 Humongous Region 分配;它不是简单的“传统老年代连续区直入”。ZGC、Shenandoah 以及它们不同版本的分代模式也有各自布局和大对象路径。

例如 G1 Region 为 4 MB 时,超过约 2 MB 的对象就可能被按 Humongous 对象处理;一个 9 MB 数组需要占用至少 3 个连续 Region,即预留 12 MB,末尾会有内部浪费。

因此看到大数组导致老年代压力时,要先确认收集器和 Region 大小,再解释日志。

五、过早晋升为什么危险

大量短命对象若被迫晋升,会带来三类成本:

  • 老年代占用增长更快,增加并发标记或 Full GC 压力;
  • 本可在下一次 Young GC 消失的对象,进入更昂贵的回收范围;
  • 高晋升速率可能使并发收集来不及完成。

但“老年代增长快”也不能只归因于年轻代太小。真实的长寿缓存、请求积压、类加载器泄漏和大对象分配都会呈现类似趋势。

诊断时应关联查看分配速率、Young GC 后存活量、年龄分布、晋升速率、老年代回收后基线和对象直方图。

六、参数应怎样验证

常见相关参数和边界如下:

参数/信息用途使用提醒
MaxTenuringThreshold限制最大晋升年龄实际阈值可更低,适用性依收集器而定
TargetSurvivorRatio影响目标 Survivor 占用不是对象达到该比例就机械晋升
SurvivorRatio影响 Eden/Survivor 比例自适应策略可能调整布局
PretenureSizeThreshold部分收集器的大对象预分配不适用于所有收集器,尤其不能直接套给 G1
PrintTenuringDistribution/统一日志观察年龄分布参数名称和日志方式随 JDK 版本变化

调参步骤应该是“记录基线 → 提出假设 → 小范围改变 → 对比相同流量窗口”,而不是把年龄阈值直接改到最大。

七、常见误区与追问

  • 误区:所有对象都必须熬满 15 次 Young GC 才晋升。 实际阈值可能动态降低,空间不足也会促使对象提前晋升。
  • 误区:所有大对象都会通过 PretenureSizeThreshold 直入老年代。 该参数只适用于部分 HotSpot 收集器,G1 使用 Humongous Region 规则。
  • 误区:年轻代越大就一定越好。 它可能降低 GC 频率,却增加单次扫描、复制和暂停成本,还会挤占老年代空间。
  • 追问:对象年龄存在哪里? HotSpot 通常把年龄编码在对象头 Mark Word 的相关位中,但具体布局受锁状态、位数和实现影响。
  • 追问:为什么实际晋升年龄小于最大年龄? 收集器会根据 Survivor 目标容量和年龄分布动态选择阈值。
  • 追问:晋升失败意味着什么? 老年代无法承接本次晋升,收集器需要走更重的回收或退化路径,最终仍无法分配时可能 OOM。
  • 追问:怎样确认是否发生过早晋升? 联合分析年龄分布、Young GC 存活量、晋升速率与老年代回收后基线。

回答任何晋升参数前先报出收集器和 JDK 版本;这一步能避免把 CMS/Serial 时代的经验错误套到 G1、ZGC 或 Shenandoah。

八、加强记忆

把晋升记成“年龄、空间、收集器特例”三条主线:对象年龄达到本轮阈值会晋升,Survivor 放不下会促使提前晋升,大对象则走收集器自己的特殊路径。MaxTenuringThreshold 只是上界之一,实际阈值可能依据年龄分布和目标 Survivor 容量动态降低,所以“固定 15 次”不成立。G1 要讲 Humongous Region,不能照搬 PretenureSizeThreshold 的传统收集器经验。线上是否过早晋升,要联合年龄分布、Young GC 存活量、晋升速率和老年代回收后基线判断。