12-JVM 调优方法论与参数速查

本篇是「JVM 与性能调优系列」第 12 篇,系列收尾。

前面 11 篇你都看了,但真到线上还是乱。因为缺一套「先想清楚再动手」的方法论。这篇把 JVM 调优收成一张图、一张表,以后遇事照着走。


一、调优的第一原则:别调优

听起来反直觉,但最重要。

80% 的「性能问题」根本不是 JVM 参数问题 ,而是代码或架构问题:无脑全表查询、缓存击穿、N+1、线程池配错、同步阻塞。这些问题靠 -Xmx 翻倍是盖不住的,反而掩盖了病灶。

而且 JVM 默认值(尤其 JDK 9+ 的 G1 自适应)已经相当聪明。对新项目:先用默认 + G1/ZGC,跑压测看指标,真有瓶颈再动参数。调优是「验证假设」,不是「堆参数碰运气」。


二、问题分类决策树

遇事先归类,再动手:

  • 吞吐不够 → 看 Parallel,或调大堆、查计算热点(火焰图)
  • 延迟高(停顿长) → G1 调 MaxGCPauseMillis,或上 ZGC
  • 内存溢出 → 走第 10 篇 OOM 流程(先分 Java OOM / OS Kill)
  • CPU 高 → 走第 11 篇(死循环 / 频繁 GC / 死锁)
  • 启动慢 / 元空间涨 → 看类加载、-XX:MaxMetaspaceSize

分类错了,工具就用错------比如 CPU 高是 GC 引起的,你却在代码里找死循环。


三、一套通用调优流程(闭环)

复制代码
定目标 → 建基线 → 找瓶颈 → 改一处验一处 → 固化+告警
  1. 定目标:吞吐优先还是延迟优先?量化指标(如「P99 停顿 < 200ms,吞吐 > 98%」)
  2. 建基线:开 GC 日志 + 压测,统计当前吞吐量、平均/最大停顿、Full GC 频率
  3. 找瓶颈:用日志、火焰图、Arthas 定位最制约指标的那一环
  4. 改一处验一处:只动一个参数,压测对比基线。有效固化,无效回滚换假设
  5. 固化 + 告警:把参数写进部署配置,把关键指标(老年代、直接内存、线程数)接告警

四、参数速查表(按用途分组)

内存

复制代码
-Xms / -Xmx            堆初始/最大(生产设相等)
-XX:MaxMetaspaceSize  元空间上限
-XX:MaxDirectMemorySize 直接内存上限

收集器

复制代码
-XX:+UseG1GC          G1(JDK9+ 默认)
-XX:+UseZGC           ZGC(大堆低延迟)
-XX:+UseSerialGC / -XX:+UseParallelGC  朴素场景

G1 关键

复制代码
-XX:MaxGCPauseMillis       停顿目标
-XX:InitiatingHeapOccupancyPercent  触发 Mixed GC 的老年代占用(默认45)
-XX:G1HeapRegionSize       Region 大小
-XX:G1ReservePercent       预留防晋升失败(默认10)

ZGC 关键

复制代码
-XX:SoftMaxHeapSize   软上限,空闲时归还 OS

诊断(强烈建议常开)

复制代码
-Xlog:gc*             统一 GC 日志
-XX:+HeapDumpOnOutOfMemoryError   出 OOM 自动留堆快照
-XX:HeapDumpPath=/path

五、监控与告警清单

调完不是结束,是开始监控:

  • 老年代使用率(持续 >80% 预警,防 Full GC)
  • Full GC 频率 / 最大停顿(>1s 立刻查)
  • 直接内存、线程数(防 OS Kill)
  • SafePoint 耗时(过长说明代码里有长循环不进安全点)

工具链:Prometheus + Grafana 采集 JMX,Arthas 在线急救,火焰图(async-profiler)定位热点。


六、JDK 版本选择

  • JDK 8 仍是很多公司的稳定基线,但 CMS 已移除、ZGC 不可用
  • JDK 17 / 21(LTS) 是新建项目的甜点区:ZGC 成熟、G1 更强、长期支持、性能更好
  • 升级有成本(依赖兼容、测试),但长远看,新收集器和新特性带来的稳定性收益通常远超迁移投入

七、系列总结

12 篇串成一条线:

复制代码
内存模型(第1篇)→ 对象布局(2)→ GC 算法(3)→ 分代(4)
→ 收集器 Serial/Parallel(5)→ CMS(6)→ G1(7)→ ZGC(8)
→ GC 日志调优(9)→ OOM 排查(10)→ CPU/死锁(11)→ 方法论(12)

从「内存长什么样」到「怎么查、怎么调」,你现在有了完整的 JVM 坐标系。调优的本质从来不是背参数,而是理解原理、用数据驱动、闭环验证


八、火焰图定位热点

GC 和内存都正常,但 CPU 高、吞吐上不去?问题在业务代码本身的热点 。这时用 async-profiler 抓火焰图:

bash 复制代码
# 采样 60 秒 CPU 火焰图
asprof -d 60 -f flamegraph.html <pid>

火焰图里最宽的那一层就是最耗时的调用栈。常见「热点」:

  • 正则 Pattern.compile 写在循环里(每次重建)
  • JSON 序列化大对象、反射调用未缓存
  • 无意义的深拷贝、重复计算

这些靠 JVM 参数永远调不好,只能改代码。火焰图是区分「JVM 问题」和「代码问题」的照妖镜。


九、常见调优误区清单

  1. 堆越大越好:超过 32G 指针压缩失效,对象头翻倍;且大堆 Full GC 更慢
  2. MaxGCPauseMillis 设极小值:反而频繁 GC、吞吐崩
  3. 只在出事时调:应在上线前压测定基线,而非半夜救火
  4. 照搬网上的「最优参数」:别人 64G 堆的参数套到你 4G 堆上必然翻车
  5. 忽视监控:调完不设告警,下次出问题又从零查起

记住:参数是手段,指标是目标,理解原理才是根本


十、调优 Checklist 速查

一张贴墙清单,覆盖绝大多数场景:

  • 先用默认 G1/ZGC 跑压测,确认真的有瓶颈
  • 开 GC 日志 + HeapDumpOnOutOfMemoryError 留现场
  • 吞吐问题 → 看 Parallel / 堆大小 / 计算热点
  • 延迟问题 → 调 MaxGCPauseMillis / 上 ZGC
  • OOM → 分 Java OOM / OS Kill,按第 10 篇流程
  • CPU 高 → 定死循环/频繁GC/死锁,按第 11 篇流程
  • 一次只动一个参数,前后压测对比
  • 固化参数 + 接告警,形成闭环

十一、从 0 到 1 建一套 JVM 监控

没监控的调优是瞎子摸象。最小可行监控:

  1. 暴露指标-XX:+UseG1GC 配合 JMX(-Dcom.sun.management.jmxremote)或用 jmx_exporter
  2. 采集:Prometheus 拉 JMX 指标(堆/各代/GC 次数耗时/线程数)
  3. 展示:Grafana 画「堆使用率、GC 停顿 P99、Full GC 频率」三张核心图
  4. 告警:老年代 >85%、Full GC >3 次/5 分、最大停顿 >1s 触发告警
  5. 急救:Arthas 常备,随时在线定位

这套建起来,你从「半夜被叫醒救火」变成「告警里提前看到趋势」,运维体验天差地别。


总结

JVM 调优第一原则「别调优」:先排除代码/架构问题,再用默认收集器跑压测。遇事走决策树归类,用「定目标→建基线→找瓶颈→改验→固化告警」闭环,一次只动一个参数。把本文的速查表和告警清单存好,下次线上抖动,照着走就不慌。

相关推荐
星空1 小时前
Map<String, String>`Map`是接口,不能直接 new
java·前端·算法
干到60岁退休的码农1 小时前
16.过滤器中处理异常响应返回
java·spring boot·mybatis
Java_2017_csdn1 小时前
StringUtils.hasText() 和 StringUtils.isNotBlank() 方法对比
java
Joy T1 小时前
Spring AI 2.0 进阶入门:RAG、Structured Output 与 Agent 信息闭环
java·人工智能·spring·rag·springai·agent入门
xiaoqiMikko2 小时前
jackson-databind 又出 4 条,Dependabot 一条都不报:2.21.5 还差 3 条
java·安全
無限進步D2 小时前
VSCode 简介+安装+JavaWeb前端常用插件+简单配置
java·开发语言·css·vscode·html·css3·插件
xiaoqiMikko2 小时前
Central 上的 Spring 5.3.41 不是官方发的:逐字节比对,官方修两条它少一条
java·安全
用户094248568032 小时前
第8章:Java 内存模型(JMM)入门——可见性、有序性、原子性
java·jvm
AI深栈2 小时前
第 9 章 · Model Context Protocol(MCP)
java·人工智能