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=N | Old/Young 比例 | 2(老年代:新生代=2:1) | -Xmn 优先级更高 |
-XX:SurvivorRatio=N | Eden/Survivor 比例 | 8(Eden:S0=8:1) | Eden Surivvor1 Surivvor2 = 8:1:1 |
-XX:MaxTenuringThreshold=N | 晋升年龄阈值 | 15(默认,最大) | 4 bit 年龄字段上限,1-15 |
-XX:TargetSurvivorRatio=N | Survivor 使用目标 | 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:+UseSerialGC | Serial + Serial Old | 所有 | 客户端、< 100M 堆 |
-XX:+UseParallelGC | Parallel Scavenge + Parallel Old | 所有 | 批处理、吞吐量优先 |
-XX:+UseConcMarkSweepGC | ParNew + CMS + Serial Old fallback | 1.5-8 (JDK 9 起标记废弃) | 不推荐新项目使用 |
-XX:+UseG1GC | G1 | JDK 7u4+ (JDK 9+ 默认) | 通用默认选择 |
-XX:+UseZGC | ZGC | JDK 11+ (15+ 生产) | 超低延迟、> 64G 堆 |
-XX:+ZGenerational | 分代 ZGC | JDK 21+ | 分代 ZGC (吞吐量更高) |
-XX:+UseShenandoahGC | Shenandoah | JDK 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=200 | 200ms | 停顿目标。值越小→GC 越频繁→吞吐量下降。200ms 是好的起点 |
-XX:G1HeapRegionSize=4m | ~1-32MB 自动 | Region 大小。需为 2 的幂。明确指定适用于大对象分配 |
-XX:InitiatingHeapOccupancyPercent=45 | 45% | 触发并发周期的堆占用率。调低→更早开始标记→更少并发失败;调高→减少 GC 开销但面临 Full GC 风险 |
-XX:G1NewSizePercent=5 | 5% | 年轻代最小占比 |
-XX:G1MaxNewSizePercent=60 | 60% | 年轻代最大占比 |
-XX:G1ReservePercent=10 | 10% | 预留空间(预防"晋升失败") |
-XX:G1HeapWastePercent=5 | 5% | 允许的堆浪费比例。调低→更积极收集 |
-XX:G1MixedGCLiveThresholdPercent=85 | 85% | Mixed GC 中 Old Region 的存活率阈值。低于此值才收集 |
-XX:G1MixedGCCountTarget=8 | 8 | 一次并发周期分几次 Mixed GC 完成 |
-XX:+G1UseAdaptiveIHOP | true (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:+ZGenerational | JDK 21+ | 分代模式(推荐) |
-XX:ZAllocationSpikeTolerance=2 | 2.0 | 分配突发容忍度。调高→更保守→更大预留 |
-XX:ZCollectionInterval=N | 无 | 强制 GC 间隔(秒)。通常不需要设置 |
-XX:ZProactive=true | true | 主动触发 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:+UseStringDeduplication | G1 字符串去重 | 将重复 String 的 char[] 指向同一个底层数组。节省堆空间(G1 特有) |
-XX:+AlwaysPreTouch | 启动时预分配物理内存 | 启动变慢,但运行期性能更好(避免运行时缺页中断) |
-XX:+ScavengeBeforeFullGC | Full GC 前先 Young GC | 默认开启。减少进入老年代的垃圾 |
-XX:+CMSScavengeBeforeRemark | CMS Remark 前 Young GC | CMS 特有。减少 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.jar16GB 堆批处理 (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.jar32GB+ 堆低延迟 (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索引**