GC 调优不是收集参数,而是一个可证伪实验:定义业务目标,记录基线,识别限制因素,只改变一个变量,再用相同负载复测。 如果没有可重复负载和回滚方案,先不要改参数。
Table of contents
定义四类目标
每次调优至少记录这些指标:
| 目标 | 示例 | 证据来源 |
|---|---|---|
| 业务延迟 | P99 < 120 ms | 入口请求指标 |
| GC 暂停 | 暂停 P99 < 40 ms | GC 日志、JFR |
| 吞吐 | ≥ 2,000 RPS | 压测端与服务端 |
| 资源 | CPU < 70%,RSS < 3 GiB | 容器与系统指标 |
平均延迟不能替代尾延迟。只优化暂停也可能降低吞吐、增加 CPU 或扩大内存占用。
建立基线
固定 JDK、镜像、参数、数据集、流量模型、预热时间和运行时长。收集:
- 分配速率与晋升速率。
- 完整 GC 后的稳定存活集。
- 暂停分布与 GC 频率。
- CPU、RSS、线程和容器节流。
- 应用吞吐、错误率和延迟分位数。
先查代码层问题:无限缓存、超大批次、重复序列化和一次性加载全量数据,通常不能靠 GC 参数修好。
一次只改一个决策
常见实验顺序如下:
- 确认堆预算能容纳稳定存活集和分配波动,并给 Native 内存留余量。
- 使用目标 JDK 的默认收集器运行基线。
- 若暂停目标不满足,确认长暂停阶段和对象原因。
- 只修改一个主要变量,例如堆大小、收集器或 G1 暂停目标。
- 运行相同负载,比较完整指标并执行至少一次重复实验。
G1 的 MaxGCPauseMillis 是软目标,不是硬实时保证。把它设置得很小可能缩小年轻代、增加收集频率并降低吞吐。JDK 25
的 G1 调优说明建议先移除
不必要的固定年轻代参数,让 G1 自适应。
保存实验记录
假设:长暂停来自 Humongous 对象分配导致的提前并发周期
基线:JDK 25.0.x,G1,Xmx=2g,P99=...,暂停 P99=...
变更:把批次缓冲从 2 MiB 拆成 256 KiB;JVM 参数不变
结果:Humongous 分配下降 ...%,P99 变为 ...
结论:接受/拒绝;回滚方式:恢复提交 ...
调优结论必须包含测试条件和失败结果。否则下一位维护者会重复同一实验或把参数复制到完全不同的服务。
下一步
阅读JDK 升级与 JVM 性能回归,把同一实验用于版本迁移门禁。