Skip to content

新生代/老年代参数设置

分代的理论基础

在动手设置参数之前,先理解为什么 JVM 要做分代。

一个重要的经验观察:大多数对象的生命周期很短

统计数据显示,在典型应用中:

  • 60%~80% 的对象是「朝生夕灭」的(创建后很快就不可达)
  • 只有 5%~10% 的对象会存活较长时间
  • 只有 1%~5% 的对象会长期存活

基于这个观察,JVM 把堆分成年轻代和老年代:

┌─────────────────────────────────────────────────┐
│                   堆(Heap)                      │
│                                                   │
│  老年代(长期存活对象)                             │
│  ┌───────────────────────────────────────────┐  │
│  │  占用约 2/3 的堆                            │  │
│  │  GC 频率低,但 Full GC 停顿时间较长         │  │
│  └───────────────────────────────────────────┘  │
│                                                   │
│  新生代(朝生夕灭对象)                            │
│  ┌───────────────┬────────────────────────────┐  │
│  │   Eden (8/10) │   Survivor S0 │ Survivor S1 │  │
│  │   新对象在这分配  │  MinorGC 中转              │  │
│  └───────────────┴────────────────────────────┘  │
└─────────────────────────────────────────────────┘

年轻代的 Minor GC 非常频繁,但停顿时间很短(通常几十毫秒)。老年代的 Full GC 频率低,但一旦发生停顿时间可能很长(数百毫秒甚至秒级)。

年轻代参数

年轻代大小

bash
# JDK 8 及之前:-Xmn 设置年轻代大小
java -Xmn2g MyApp

# JDK 9+:可以用比例设置
java -XX:NewRatio=2 MyApp   # 老年代:年轻代 = 2:1
                            # 如果堆 4GB,年轻代约 1.3GB

Eden 与 Survivor 比例

bash
# SurvivorRatio = Eden / Survivor
# 默认值 8,表示 Eden : Survivor = 8 : 1 : 1
# 年轻代 1GB 时:Eden=800MB,S0=100MB,S1=100MB

# 如果有很多对象需要存活较长时间(但不到老年代阈值)
# 可以增大 Survivor 区
java -XX:SurvivorRatio=4 MyApp
# 年轻代 1GB:Eden=640MB,S0=180MB,S1=180MB

晋升阈值

bash
# 对象经历多少次 Minor GC 后晋升老年代
# 默认值 15(JDK 8 及之前最大是 15)
java -XX:MaxTenuringThreshold=15 MyApp

# JDK 9+ 最大值变成 31
java -XX:MaxTenuringThreshold=31 MyApp

# 设置晋升阈值=1 表示对象第一次 MinorGC 后直接晋升
# 适用于大多数对象会在第一次 MinorGC 后失效的场景
java -XX:MaxTenuringThreshold=1 MyApp

大对象直接进入老年代

bash
# 超过这个大小的对象直接在老年代分配
# 默认值 0,表示不启用
java -XX:PretenureSizeThreshold=1048576 MyApp
# 超过 1MB 的对象直接在老年代分配

老年代参数

老年代大小

bash
# 老年代大小 = 堆大小 - 年轻代大小
# -Xmx=4g -Xmn=1g → 老年代约 3GB
java -Xmx4g -Xmn1g MyApp

# 可以用 NewRatio 间接控制
java -XX:NewRatio=3 MyApp
# 老年代:年轻代 = 3:1,堆 4GB 时年轻代 1GB,老年代 3GB

老年代空间分配担保

在 Minor GC 之前,JVM 会检查老年代最大可用连续空间是否大于年轻代所有对象的总空间。如果小于,JVM 会根据 HandlePromotionFailure 参数决定是否允许担保失败。

bash
# JDK 8 Update 24 之后,HandlePromotionFailure 参数被移除
# JVM 总是假设担保会成功,老年代空间不足时直接触发 Full GC

# 担保失败的逻辑:
# 老年代最大连续空间 >= 年轻代对象总大小 → Minor GC,安全
# 老年代最大连续空间 < 年轻代对象总大小 → Full GC(担保失败)

G1 的特殊参数(JDK 11+)

G1 不再使用传统的分代结构,而是把堆划分为多个 Region:

bash
# 设置 G1 Region 大小
java -XX:G1HeapRegionSize=4m MyApp
# 最小 1MB,最大 32MB,必须是 2 的幂

# 目标停顿时间
java -XX:MaxGCPauseMillis=200 MyApp  # 目标最大 GC 停顿 200ms

# GC 时间占比
java -XX:GCTimeRatio=19 MyApp  # GC 时间占总时间不超过 1/(1+19) = 5%

调优实战

场景一:高并发 Web 服务

这类服务的特点是对象生命周期短,但请求量大。

bash
# 推荐配置
java -Xms4g -Xmx4g \           # 固定堆大小,避免运行时调整
     -Xmn2g \                  # 年轻代 2GB,足够容纳大量短期对象
     -XX:SurvivorRatio=8 \    # 默认比例,Eden:Survivor = 8:1:1
     -XX:MaxTenuringThreshold=8 \  # 略微降低晋升阈值,减少老年代压力
     -XX:+UseParallelGC \      # 使用 Parallel GC,吞吐量优先
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     MyApp

场景二:大数据处理/批处理

这类场景需要高吞吐量,GC 停顿可以稍长。

bash
# 推荐配置
java -Xms8g -Xmx8g \
     -Xmn6g \                  # 年轻代较大,提高吞吐量
     -XX:SurvivorRatio=4 \     # 增大 Survivor 区,减少晋升老年代的对象
     -XX:MaxTenuringThreshold=20 \
     -XX:+UseParallelOldGC \   # 老年代也用并行 GC
     MyApp

场景三:低延迟服务(金融交易、游戏服务器)

bash
# 推荐配置
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \            # G1GC,平衡吞吐和延迟
     -XX:MaxGCPauseMillis=100 \ # 目标最大停顿 100ms
     -XX:G1HeapRegionSize=8m \  # 较大的 Region
     -XX:InitiatingHeapOccupancyPercent=45 \  # 触发并发 GC 的堆使用率
     MyApp

场景四:容器环境(Kubernetes)

bash
# 推荐配置
java -XX:+UseContainerSupport \  # JDK 10+,让 JVM 自动感知容器资源
     -XX:MaxRAMPercentage=75 \    # 堆最多使用 75% 的容器内存
     -XX:InitialRAMPercentage=75 \
     -XX:+UseG1GC \
     MyApp

注意:在容器环境中,不要用 -Xmx 硬编码值,否则无法充分利用容器分配的内存。

参数计算示例

假设总堆 4GB,想设置年轻代占 1/3:

bash
# 方法一:-Xmn 直接指定
java -Xmx4g -Xmn1360m MyApp

# 方法二:-XX:NewRatio
java -Xmx4g -XX:NewRatio=2 MyApp
# NewRatio=2 表示老年代:年轻代 = 2:1
# 即年轻代 = 堆 * 1/(NewRatio+1) = 4GB * 1/3 ≈ 1.33GB

# Survivor 区大小计算
# SurvivorRatio=8,Eden:Survivor = 8:1:1
# 年轻代 1.33GB:Eden ≈ 1.07GB,每个 Survivor ≈ 133MB

本节小结

分代参数设置的核心原则:

参数作用默认值
-Xmn / -XX:NewRatio年轻代大小新生代约占堆的 1/3
-XX:SurvivorRatioEden/Survivor 比例8(Eden: S0: S1 = 8:1:1)
-XX:MaxTenuringThreshold晋升老年代的年龄15(JDK 8)
-XX:PretenureSizeThreshold大对象阈值0(不启用)

调优的关键是理解自己应用的内存分配模式

  • 对象短命 → 增大年轻代,降低晋升阈值
  • 对象长寿 → 增大老年代
  • 低延迟 → 使用 G1GC,设置目标停顿时间
  • 高吞吐 → 使用 Parallel GC,增大年轻代

下一节,我们来看 对象分配过程(一般/特殊情况)

基于 VitePress 构建