先确认是否真需调优:排除应用层(死循环、锁竞争)、系统层(i/o、网络、流量突增)干扰;再用jstat -gc观察fgct和gct占比,仅当5分钟内fgct>0或gct>10%才深入分析;-xms与-xmx必须相等且不超容器内存限制;选对gc器(如web服务用g1、批处理用parallel)比调参数更重要;元空间和线程栈配置常被忽略,需针对性设限。

先看 GC 日志和 jstat 输出,别急着改参数。 90% 的 JVM 性能问题靠盲调参数只会让情况更糟——真正该做的,是确认当前有没有问题、问题出在哪一层。
怎么快速判断是不是真要调优
不是所有高 CPU 或慢响应都该归咎于 JVM。先排除应用层和系统层干扰:
- 用
top -H -p <pid></pid>看是否单个线程持续占满 CPU,再结合jstack <pid></pid>查栈,确认是不是死循环或锁竞争 - 检查磁盘 I/O(
iostat -x 1)和网络延迟(curl -w "@curl-format.txt" -o /dev/null -s http://...),避免误判为 GC 导致的卡顿 - 确认是否刚上线新功能或流量突增,而不是 JVM 本身扛不住
- 用
jstat -gc <pid> 2000</pid>持续观察:如果FGCT(Full GC 次数)在 5 分钟内增长 > 0,或GCT(GC 总耗时)占比 > 10%,才值得深入
-Xms 和 -Xmx 必须设成一样
默认值(-Xms 是物理内存 1/64,-Xmx 是 1/4)在生产环境基本不可用。动态扩容堆会触发额外 GC 和内存碎片,尤其在容器中容易被 OOMKilled。
- 设为相同值:例如
-Xms4g -Xmx4g,避免运行时调整堆边界 - 值不能超过容器内存限制(如 Docker 的
--memory=4g),否则 cgroup 会直接 kill 进程 - 不要盲目堆大:4g 堆配 G1GC 通常比 8g 更稳;超大堆(>16g)才考虑 ZGC,但需 JDK ≥17
选对垃圾回收器比调参数重要十倍
OpenJDK 17+ 默认是 G1,但它不是万能解。选错 GC,调再多 -XX:MaxGCPauseMillis 都没用:
- Web API 类服务(延迟敏感、堆 ≤8g):坚持用
-XX:+UseG1GC,加-XX:MaxGCPauseMillis=200作目标,别碰-XX:G1HeapRegionSize - 批处理或后台任务(吞吐优先):换
-XX:+UseParallelGC,它不承诺停顿,但单位时间完成更多 GC - 老年代对象多、晋升快(比如缓存服务):检查
jstat中OU(老年代使用量)是否缓慢上涨,考虑调小-XX:NewRatio或增大-Xmn,而非换 GC - 别用 CMS:JDK 14 起已移除,且它无法处理浮动垃圾,容易突发 Full GC
元空间和线程栈最容易被忽略
这两个地方出问题,不会报 OutOfMemoryError: Java heap space,而是直接 crash 或静默失败:
- Spring Boot + 大量反射/动态代理?加
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m,防止元空间无限扩张吃光内存 - 高并发短连接(如网关)?
-Xss256k比默认 1m 更安全,5000 线程可省下近 4GB 内存 - 日志里出现
java.lang.OutOfMemoryError: Compressed class space?说明类加载太多,必须限制-XX:CompressedClassSpaceSize - 容器中启动失败但无明确错误?大概率是
-Xss或元空间超限被 cgroup 杀掉,查dmesg | grep -i "killed process"
真正卡住的地方,往往不在堆大小或 GC 策略,而在元空间泄漏、线程栈溢出、或者容器内存限制与 JVM 参数不匹配。数据没出来之前,任何“我觉得该调”的操作,都是在给故障埋雷。











