jvm调优核心是合理设置堆内存(-xms与-xmx相等)、新生代大小、元空间上限,选用匹配场景的gc器(如g1),开启gc日志与堆转储,并通过压测验证效果。

堆内存设不对,GC就天天找你麻烦
JVM调优最直接的入口就是堆内存参数——-Xms 和 -Xmx。很多人设成 -Xms512m -Xmx4g,结果运行中堆不断扩容缩容,触发频繁 GC,停顿飙升。这不是“省内存”,是给 GC 制造震荡。
- 生产环境强烈建议
-Xms与-Xmx设为相等值(如-Xms4g -Xmx4g),消除动态伸缩带来的 GC 波动 - 新生代别拍脑袋定:用
jstat -gc <pid></pid>观察EC/EU和OC/OU比例,若老年代增长快、年轻代回收后存活对象多,说明-Xmn太小或-XX:SurvivorRatio不合理 - JDK 8+ 默认永久代已淘汰,但元空间(Metaspace)仍可能引发 Full GC:如果
MC接近MG且OU持续上涨,加-XX:MaxMetaspaceSize=256m并配-XX:+PrintGCDetails确认是否元空间耗尽
选错垃圾回收器,再调参数也白搭
不是所有机器都适合 G1;也不是所有业务都能扛住 CMS 的浮动垃圾风险。回收器选错,等于在高速路上开拖拉机——参数调得再细,也跑不快。
- CPU 核数 ≤ 2 或吞吐优先 → 用
-XX:+UseParallelOldGC(Parallel Scavenge + Parallel Old),别碰并发回收器 - 响应敏感、堆 ≥ 6G、JDK ≥ 1.8 →
-XX:+UseG1GC是当前最稳妥选择,但必须配-XX:MaxGCPauseMillis=100(目标值,非保证值),否则 G1 会退化成吞吐模式 - 禁用
-XX:+UseConcMarkSweepGC:CMS 在 JDK 9 已废弃,JDK 14 彻底移除;线上还在用的系统务必升级或迁移到 G1/ZGC - 别信“全开并发”:
-XX:+UseStringDeduplication这类优化只对大量重复字符串有效,盲目开启反而增加 CPU 开销
线程栈和 OOM 日志,一漏就卡死排查脖子
堆溢出(java.lang.OutOfMemoryError: Java heap space)好定位,但栈溢出(java.lang.StackOverflowError)或直接 OOM 后进程静默退出,往往因为关键日志没留——等你发现时,现场早没了。
- 强制保留崩溃现场:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heap.hprof,路径必须有写权限,否则 dump 失败静默丢弃 - 每个线程默认栈大小是 1M(64 位 Linux),高并发服务线程数超 500 时,光栈就吃掉 500MB+ 内存;用
-Xss256k更安全,但注意递归深度不能超过 ~200 层 -
jstack必须配合top -H -p <pid></pid>使用:先看哪个线程 tid 占 CPU 高,再用jstack <pid> | grep <nid></nid></pid>定位具体栈帧,否则满屏线程信息根本没法筛
监控不落地,调优就是闭眼猜
没有基线数据的调优,就像蒙眼换轮胎——你以为拧紧了,其实松着。阿里云案例里提到 80% 性能问题靠 GC 日志就能定位,前提是日志得开着、存得住、看得懂。
立即学习“Java免费学习笔记(深入)”;
- 必加 GC 日志:
-Xlog:gc*:file=/data/logs/gc.log:time,uptime,pid,tags(JDK 11+),JDK 8 用-XX:+PrintGCDetails -Xloggc:/data/logs/gc.log - 别只看“GC 次数”:重点盯
G1 Evacuation Pause耗时、Full GC是否出现、Metaspace区是否持续增长;用gcviewer或gceasy.io可视化分析,比人肉扫日志快十倍 - 线程数暴涨?查
jstat -thread <pid></pid>的peak和current差值;再结合代码里new Thread()或未配置 core/max 的线程池,才是根因
真正的调优难点不在参数本身,而在“改完之后怎么确认它真的变好了”。一次调整必须对应一次压测 + GC 日志对比 + 关键接口 P99 延迟验证。少一个环节,就只是把问题从内存挪到了 CPU,或者从 GC 挪到了线程锁。











