调优漏斗:参数在最底层
遇到性能问题,从上往下过漏斗:架构层(缓存有没有、异步有没有、读写分离有没有)→ 代码层(循环里查库、无界集合、串行调用)→ JVM 参数。漏斗上层能解决九成问题——GC 频繁 80% 是"分配太多"而不是"回收太慢",把一次请求创建的临时对象砍半,比任何 GC 参数都立竿见影。调参是还完架构与代码的债之后才轮到的事。
目标先行:不可能三角
吞吐量、延迟、内存占用,三者互相牵制:堆越大单次 GC 越久但次数越少(吞吐↑停顿↑),并发收集器停顿短但总 CPU 开销大(延迟↑吞吐↓)。先定业务目标再选收集器:批处理要吞吐(Parallel),在线接口要 P99 停顿(G1/ZGC)。没有目标的调优就是乱拧旋钮,拧完也不知道是变好还是变坏。
一份务实的生产模板
-Xms4g -Xmx4g # 初始=最大,避免堆伸缩抖动
-XX:+UseG1GC # JDK 9+ 默认,显式写出防混淆
-XX:MaxGCPauseMillis=200 # 停顿目标,别设太狠(<100ms 伤吞吐)
-XX:MaxMetaspaceSize=512m # 元空间上限,防动态类吃穿内存
-XX:+HeapDumpOnOutOfMemoryError # OOM 自动留现场
-XX:HeapDumpPath=/data/dump/
-Xlog:gc*:file=/data/log/gc.log:time:filecount=10,filesize=50M
# 新生代大小交给 G1 自适应,手调 -Xmn 反而限制它的停顿模型这份模板覆盖 80% 的常规服务。真正的调优是从它出发,看日志找具体瓶颈再动对应的旋钮——比如晋升过快调 Survivor/分代阈值,Humongous 频繁就调大 Region 尺寸,而不是背参数大全。
调优流程:一次只动一个旋钮
规范动作是:压测造流量 → 采集基线(GC 频率/停顿/吞吐)→ 单变量调整(一次只改一个参数)→ 同样压测对比 → 有效则保留、无效则回滚。多变量同调是调优大忌——改善了 A 指标却说不清是哪个参数的功劳,下次出问题也没有可复现的基线。这跟全链路压测的容量规划是一个思路:结论必须来自受控对比。
三个常见误区
抄大厂参数:人家 64G 堆的参数搬到你 4G 堆就是灾难,场景不同没有可比性。盲目加大堆:堆大了 Full GC 更疼——停顿时间随堆涨,4G 的 Full GC 秒级,32G 的分分钟能把服务判死刑;加大堆前先确认不是泄漏。只盯堆:元空间、直接内存、线程栈都在 -Xmx 之外,RSS 远超 -Xmx 是常态——容器里给 JVM 的内存配额要把这些算进去,否则容器 OOM Killer 杀进程,日志里连个 OOM 都没有。下一篇看 JVM 另一个隐身机制:JIT 是怎么悄悄改写你的代码的。
评论 (0)