Skip to content

05.5 - GC 调优参数速查

定位: 生产环境中最常用的 GC 参数——从堆大小到收集器选型,一份精心筛选的参数速查 面试高频度: ⭐⭐⭐⭐ 考查方式: 常用 GC 调优参数、Xms/Xmx 为什么相等、MaxGCPauseMillis 怎么设


注意: JVM 有 700+ GC 参数。本页只列出生产中真正有用的。其他参数要么是调试专用,要么是历史遗留,不需要关注。


一、堆大小参数

参数作用推荐值说明
-Xms<N>初始堆大小等于 -Xmx与 -Xmx 相等,避免堆动态调整
-Xmx<N>最大堆大小物理内存的 50%-80%留 20%-50% 给 OS/Metaspace/直接内存
-Xmn<N>新生代大小堆的 1/3 ~ 1/4太小=过早晋升;太大=Young GC 停顿长
-XX:MetaspaceSize=N元空间初始大小256m避免 Metaspace 扩容导致的 Full GC
-XX:MaxMetaspaceSize=N元空间最大大小512m(大部分应用)防止元空间无限增长
-XX:NewRatio=NOld/Young 比例2(老年代:新生代=2:1)-Xmn 优先级更高
-XX:SurvivorRatio=NEden/Survivor 比例8(Eden:S0=8:1)Eden Surivvor1 Surivvor2 = 8:1:1
-XX:MaxTenuringThreshold=N晋升年龄阈值15(默认,最大)4 bit 年龄字段上限,1-15
-XX:TargetSurvivorRatio=NSurvivor 使用目标50(默认 50%)超过此比例,高于阈值的对象直接晋升
-XX:+UseAdaptiveSizePolicy自适应代大小Parallel 默认开启JVM 动态调整代大小。G1 不用此参数

❓-Xms 和 -Xmx 为什么要设成一样?

java
// 如果不相等:
-Xms512m -Xmx2048m

// 运行过程:
// 堆 512M → 对象越创建越多 → 堆开始扩容 (扩容 = Full GC!) → 512M→1024M
//                                                              ↑ Full GC (几秒的停顿)
// 扩容再触发一次 Full GC → 1024M→2048M
//                                                                ↑ 又多停几秒

// // 高峰期结束后堆又要缩小:
// 缩容又触发 Full GC
// = 性能白白浪费在堆的"忽大忽小"上

关键理由:避免堆扩容/缩容引发的 Full GC。堆扩容会触发一次完整的"伸缩"GC,对于服务来说这完全是浪费。


二、收集器选择参数

参数启用收集器JDK 版本推荐场景
-XX:+UseSerialGCSerial + Serial Old所有客户端、< 100M 堆
-XX:+UseParallelGCParallel Scavenge + Parallel Old所有批处理、吞吐量优先
-XX:+UseConcMarkSweepGCParNew + CMS + Serial Old fallback1.5-8 (JDK 9 起标记废弃)不推荐新项目使用
-XX:+UseG1GCG1JDK 7u4+ (JDK 9+ 默认)通用默认选择
-XX:+UseZGCZGCJDK 11+ (15+ 生产)超低延迟、> 64G 堆
-XX:+ZGenerational分代 ZGCJDK 21+分代 ZGC (吞吐量更高)
-XX:+UseShenandoahGCShenandoahJDK 12+超低延迟、非 64 位场景

收集器选择决策树

系统部署

    ├─ 堆 < 100MB 或 单核 CPU ──────────────────────────→ Serial

    ├─ 批处理/离线作业(吞吐量优先)──────────────────────→ Parallel

    ├─ Web 服务/API 服务器(通用)──────────┐
    │                                      ├─ JDK 8 → G1 (手动打开)
    │                                      ├─ JDK 9+ → G1 (默认)
    │                                      └─ 大堆(>64G)且延迟敏感 → ZGC

    ├─ 低延迟优先(高频交易/实时)─────┐
    │                                  ├─ JDK 11-20 → ZGC
    │                                  ├─ JDK 21+ → 分代 ZGC
    │                                  └─ 非 64 位 → Shenandoah

    └─ 不确定 ───────────────────── G1 (JDK 9+ 默认 = 很多大厂的选择)

三、G1 专属参数

参数默认值说明
-XX:MaxGCPauseMillis=200200ms停顿目标。值越小→GC 越频繁→吞吐量下降。200ms 是好的起点
-XX:G1HeapRegionSize=4m~1-32MB 自动Region 大小。需为 2 的幂。明确指定适用于大对象分配
-XX:InitiatingHeapOccupancyPercent=4545%触发并发周期的堆占用率。调低→更早开始标记→更少并发失败;调高→减少 GC 开销但面临 Full GC 风险
-XX:G1NewSizePercent=55%年轻代最小占比
-XX:G1MaxNewSizePercent=6060%年轻代最大占比
-XX:G1ReservePercent=1010%预留空间(预防"晋升失败")
-XX:G1HeapWastePercent=55%允许的堆浪费比例。调低→更积极收集
-XX:G1MixedGCLiveThresholdPercent=8585%Mixed GC 中 Old Region 的存活率阈值。低于此值才收集
-XX:G1MixedGCCountTarget=88一次并发周期分几次 Mixed GC 完成
-XX:+G1UseAdaptiveIHOPtrue (JDK 9+)自适应 IHOP——AI 式的自动调整

MaxGCPauseMillis 怎么设?

设 200ms (默认):
  → 每次 Young GC 选适量的 Region,保证总时间 ≤ 200ms
  → 选得更少、次数更多 → GC 频率上升 → 吞吐量略降
  → 适合 Web 服务

设 50ms:
  → 每次选更少 Region → GC 频率显著上升
  → 吞吐量明显下降 (~5-10%)
  → 适合延迟极度敏感的服务

设 500ms:
  → 每次选更多 Region → GC 频率下降
  → 吞吐量上升
  → 单次停顿可能 500ms,适合可以忍受长停顿的场景

原则: 不要追求极低的 MaxGCPauseMillis。200ms 是 G1 的设计平衡点。设到 20ms 只会让 GC 频率高得无法承受。


四、ZGC 专属参数

参数默认值说明
-XX:+UseZGC关闭启用 ZGC
-XX:+ZGenerationalJDK 21+分代模式(推荐)
-XX:ZAllocationSpikeTolerance=22.0分配突发容忍度。调高→更保守→更大预留
-XX:ZCollectionInterval=N强制 GC 间隔(秒)。通常不需要设置
-XX:ZProactive=truetrue主动触发 GC(即使堆不满)

为什么 ZGC 的参数这么少?

因为 ZGC 的设计理念是"自调节"——它不需要手动调整 Survivor 大小、晋升阈值、IHOP 等。ZGC 的参数少反映了它的自适应性。


五、GC 日志参数

JDK 8

bash
-XX:+PrintGCDetails                              # GC 详情
-XX:+PrintGCDateStamps                           # 日期时间戳
-XX:+PrintGCApplicationStoppedTime               # 应用停顿时间
-XX:+PrintTenuringDistribution                   # 对象年龄分布(调试晋升问题)
-XX:+PrintHeapAtGC                               # GC 时堆信息(非常详细,只在短时间调试用)
-Xloggc:/path/to/gc.log                          # 输出文件

JDK 9+

bash
# 标准配置:
-Xlog:gc*=info:file=gc.log:time,uptime,level,tags:filecount=10,filesize=100M

# 诊断晋升问题:
-Xlog:gc*,gc+age=trace,gc+promotion=debug:file=gc-detail.log:time,uptime

# 调试 Young GC 各阶段:
-Xlog:gc+phases=debug:file=gc-phases.log:time,uptime

# 最详细的 (仅用于短期诊断):
-Xlog:gc*=debug:file=gc-debug.log:time,uptime

六、其他关键参数

参数作用风险/说明
-XX:+DisableExplicitGC禁用 System.gc()强烈推荐。NIO/DirectByteBuffer 可能通过 System.gc() 触发 Full GC
-XX:+ParallelRefProcEnabled并行处理 Reference减少 Reference 处理阶段的停顿
-XX:+UseStringDeduplicationG1 字符串去重将重复 String 的 char[] 指向同一个底层数组。节省堆空间(G1 特有)
-XX:+AlwaysPreTouch启动时预分配物理内存启动变慢,但运行期性能更好(避免运行时缺页中断)
-XX:+ScavengeBeforeFullGCFull GC 前先 Young GC默认开启。减少进入老年代的垃圾
-XX:+CMSScavengeBeforeRemarkCMS Remark 前 Young GCCMS 特有。减少 Remark 停顿时间
-XX:+UseContainerSupport容器感知内存设置JDK 10+ 默认开启。Docker/K8s 中正确识别 cgroup 限制

生产中必须带的参数

bash
# 生产环境 JDK 17+ G1 最小配置
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+DisableExplicitGC \
     -Xlog:gc*=info:file=/var/log/gc.log:time,uptime,level,tags:filecount=10,filesize=100m \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/heapdump.hprof \
     -jar myapp.jar

七、生产配置模板

4GB 堆 Web 应用 (G1)

bash
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=256m \
     -XX:+DisableExplicitGC \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/opt/app/logs/heapdump.hprof \
     -Xlog:gc*=info:file=/opt/app/logs/gc.log:time,uptime:filecount=5,filesize=50m \
     -jar app.jar

16GB 堆批处理 (Parallel)

bash
java -Xms16g -Xmx16g \
     -XX:+UseParallelGC \
     -XX:MaxGCPauseMillis=500 \
     -XX:GCTimeRatio=19 \
     -XX:ParallelGCThreads=8 \
     -XX:+UseAdaptiveSizePolicy \
     -XX:+DisableExplicitGC \
     -Xlog:gc*=info:file=/opt/app/logs/gc.log:time,uptime:filecount=5,filesize=50m \
     -jar batch.jar

32GB+ 堆低延迟 (ZGC)

bash
java -Xms32g -Xmx32g \
     -XX:+UseZGC \
     -XX:ZAllocationSpikeTolerance=2.0 \
     -XX:ConcGCThreads=4 \
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=256m \
     -XX:+DisableExplicitGC \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/opt/app/logs/heapdump.hprof \
     -Xlog:gc*=info:file=/opt/app/logs/gc.log:time,uptime:filecount=5,filesize=50m \
     -jar app.jar

八、调优决策树

"我的应用 OOM 了"

OOM
 ├─ Heap space OOM:
 │    └─ -XX:+HeapDumpOnOutOfMemoryError
 │          → 分析 heap dump → 找泄漏点

 ├─ Metaspace OOM:
 │    └─ -XX:MaxMetaspaceSize=256m (限制)
 │    └─ 检查是否类加载泄漏 (CGLIB 动态类,Groovy 等)

 ├─ Direct buffer memory OOM:
 │    └─ -XX:MaxDirectMemorySize=256m (限制)
 │    └─ 检查 DirectByteBuffer 是否没释放

 └─ GC overhead limit exceeded:
      └─ 98% CPU 花在 GC 但回收 < 2% → 堆太小或内存泄漏
      └─ 增大 -Xmx 或 修复泄漏

"Full GC 频繁"

Full GC 频繁

  ├─ System.gc() 触发的:
  │    └─ -XX:+DisableExplicitGC (禁用)
  │        注意: NIO DirectByteBuffer 依赖 GC 来触发 Cleaner
  │        替代: 用 cleaner.remove() 或增大直接内存

  ├─ 老年代满 → 晋升太快:
  │    ├─ 检查 -XX:+PrintTenuringDistribution
  │    ├─ 增大 -Xmn (给 Young 更多空间)
  │    ├─ 调低 MaxTenuringThreshold?
  │    └─ 增大 SurvivorRatio?

  ├─ 大对象直接进老年代:
  │    └─ 检查代码中过大的数组/集合

  └─ CMS 并发模式失败:
       └─ 调低 CMSInitiatingOccupancyFraction
       └─ 或直接切 G1/ZGC

"延迟太高"

延迟太高 (用户感受到卡顿)

  ├─ Young GC 停顿 > 200ms:
  │    ├─ 减小 -Xmn? 不——这会让更多对象留在老年代
  │    ├─ 使用 G1 的 MaxGCPauseMillis 控制单次停顿
  │    └─ 确认没有 Survivor 溢出 (PrintTenuringDistribution)

  ├─ Full GC 停顿 > 3s:
  │    ├─ Parallel → G1 (让停顿可预测)
  │    ├─ G1 → ZGC (亚毫秒级停顿)
  │    └─ 确认堆大小合理

  └─ CMS Remark 阶段慢:
       └─ -XX:+CMSScavengeBeforeRemark

九、面试视角

追问答案要点
常用的 GC 调优参数?堆大小(-Xms/-Xmx/-Xmn)、收集器选择(-XX:+UseG1GC)、停顿目标(MaxGCPauseMillis)、日志(-Xlog:gc*)、异常处理(-XX:+HeapDumpOnOutOfMemoryError)、禁用 System.gc()(-XX:+DisableExplicitGC)
-Xms 和 -Xmx 为什么要设成一样?避免堆扩容/缩容引发的 Full GC。扩容和缩容都需要 Full GC,生产中应避免这种不必要的性能损失
G1 的 MaxGCPauseMillis 怎么设?200ms 是默认平衡点。设太小(<50ms)→ GC 频率高、吞吐量下降;设太大(>500ms)→ 单次停顿长但吞吐量高。不要刻意追求极低的值
DisableExplicitGC 为什么重要?框架和第三方库可能调用 System.gc(),触发 Full GC。禁用后 DirectByteBuffer 的回收依赖 Cleaner 机制或 JVM 自动触发
PrintTenuringDistribution 有什么用?打印对象年龄分布,用来判断 Survivor 是否够用。如果大部分对象在 age 0-1 就被晋升,说明 Survivor 太小或 MaxTenuringThreshold 太低
怎么判断 G1 的 IHOP 设置是否正确?观察日志中并发周期后的 Old 占用。如果并发周期结束后 Old 占用仍 > 45%,说明 IHOP 偏高,容易触发 Full GC。如果一直不到 45% 就开始并发标记,说明 IHOP 偏低
AlwaysPreTouch 是做什么的?启动时把 -Xmx 的内存全部提交物理内存,避免运行期缺页中断。代价是启动变慢。适用于追求启动后性能稳定的场景
ZGC 参数为什么比 G1 少?ZGC 的设计目标之一是"自适应"——它不需要手动调整年轻代大小、IHOP、Survivor 比例等。大部分参数由 ZGC 自动管理
堆大小 = 物理内存的百分之多少合适?一般 50%-80%,剩下留给 OS(文件缓存)、Metaspace、直接内存、线程栈。例如 64GB 机器,-Xmx 设 48GB(75%)比较常见

📚 相关链接

  • **垃圾收集器详解(Serial到ZGC)** — 各收集器原理,理解参数为什么存在
  • **GC日志分析** — 先看日志,再调优。日志是调优的输入
  • **OOM排查方法论** — OOM 后的堆转储分析
  • **FullGC频繁排查方法论** — Full GC 的深度排查流程
  • ← 返回 **GC索引**

Knowledge4J — Java 知识库