bigywater.tech
Java · 145

垃圾回收:对象什么时候进入老年代

「对象什么时候进入老年代?」常见回答是「年龄超过 15」,但真实的 JVM 比这复杂。这篇用 Serial GC 的日志把对象的年龄、有效阈值、大对象直接分配和幸存区溢出一个个量出来(G1 的 Region 与停顿目标见下一篇)。

age 1→2→3→晋升
小对象在幸存区里的年龄变化(阈值 3)
阈值 3 → 1
幸存区装不下时有效晋升阈值自动降低
1056 → 3104 KB
2 MB 数组在 PretenureSizeThreshold=1m 下直接进老年代

年龄与晋升

实测 · 本机执行输出
  GC #1:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 494)];GC 后老年代 1056 KB,幸存区 494 KB
  GC #2:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 233), (2, 490)];GC 后老年代 1056 KB,幸存区 724 KB
  GC #3:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 233), (3, 490)];GC 后老年代 1056 KB,幸存区 724 KB
  GC #4:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 0), (3, 233)];GC 后老年代 1546 KB,幸存区 234 KB
  GC #5:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 0)];GC 后老年代 1780 KB,幸存区 0 KB
  GC #6:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(3, 0)];GC 后老年代 1780 KB,幸存区 0 KB
  GC #7:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [];GC 后老年代 1781 KB,幸存区 0 KB

动态阈值

实测 · 本机执行输出
  GC #1:有效晋升阈值 1(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 1262)];GC 后老年代 1056 KB,幸存区 1262 KB
  GC #2:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 233)];GC 后老年代 2314 KB,幸存区 233 KB
  GC #3:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 233)];GC 后老年代 2314 KB,幸存区 233 KB
  GC #4:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 0), (3, 233)];GC 后老年代 2314 KB,幸存区 234 KB
  GC #5:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 0)];GC 后老年代 2548 KB,幸存区 0 KB
  GC #6:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [(3, 0)];GC 后老年代 2548 KB,幸存区 0 KB
  GC #7:有效晋升阈值 3(上限 3),幸存区期望 1024 KB,幸存区里 (age, KB) = [];GC 后老年代 2549 KB,幸存区 0 KB

大对象

实测 · 本机执行输出
  分配前:老年代 1056 KB,Eden 1966 KB,GC 次数 0
  分配 512 KB 数组后:老年代 1056 KB,Eden 2806 KB
  再分配 2 MB 数组后:老年代 3104 KB,Eden 3134 KB,GC 次数 0

幸存区溢出

实测 · 本机执行输出
  SurvivorRatio=8:
  GC #1:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 238)];GC 后老年代 4128 KB,幸存区 238 KB
  GC #2:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 233), (2, 234)];GC 后老年代 4128 KB,幸存区 468 KB
  GC #3:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 233), (3, 234)];GC 后老年代 4128 KB,幸存区 468 KB
  GC #4:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(1, 0), (3, 233), (4, 234)];GC 后老年代 4128 KB,幸存区 469 KB
  GC #5:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(2, 0), (4, 233), (5, 234)];GC 后老年代 4128 KB,幸存区 469 KB
  GC #6:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(3, 0), (5, 233), (6, 234)];GC 后老年代 4128 KB,幸存区 469 KB
  GC #7:有效晋升阈值 15(上限 15),幸存区期望 1024 KB,幸存区里 (age, KB) = [(4, 0), (6, 233), (7, 234)];GC 后老年代 4128 KB,幸存区 469 KB
  SurvivorRatio=2:
  GC #1:有效晋升阈值 1(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(1, 3310)];GC 后老年代 1056 KB,幸存区 3310 KB
  GC #2:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(1, 233)];GC 后老年代 4362 KB,幸存区 233 KB
  GC #3:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(2, 233)];GC 后老年代 4362 KB,幸存区 233 KB
  GC #4:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(3, 233)];GC 后老年代 4362 KB,幸存区 233 KB
  GC #5:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(1, 0), (4, 233)];GC 后老年代 4362 KB,幸存区 234 KB
  GC #6:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(2, 0), (5, 233)];GC 后老年代 4362 KB,幸存区 234 KB
  GC #7:有效晋升阈值 15(上限 15),幸存区期望 2560 KB,幸存区里 (age, KB) = [(3, 0), (6, 233)];GC 后老年代 4362 KB,幸存区 234 KB

分步演示

对象晋升
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,Serial GC(-XX:+UseSerialGC -Xmn20m -Xms64m -Xmx64m)
    • 没有验证:G1、ZGC 等其它回收器的晋升规则(G1 的晋升是按 Region 与年龄表,没有在本篇验证)、Parallel GC 的自适应大小策略、-XX:+UseAdaptiveSizePolicy、更大的堆下的行为;幸存区里的约 233 KB 底噪是 JVM 自己的对象,没有逐个确认来源
    京ICP备2026067502号-1京公网安备11010502063487号