news 2026/2/2 5:25:17

JVM调优思路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM调优思路

JVM 调优思路(从“瞎调参数”到“可验证的工程方法”)

这份文档讲的是方法论 + 常见场景的落地步骤。JVM 调优不是背参数,而是:先测、再定位、再改、再验证
默认以服务端 Java(Spring Boot / 微服务 / 容器)为主,兼顾 JDK8~JDK17 常见差异。


1. 先把“调优目标”说清楚(不然你永远在乱跑)

调优的目标通常就三类,选一个当主目标,别三心二意:

  1. 低延迟:P99/P999 响应时间稳定,GC 暂停可控
  2. 高吞吐:单位时间处理更多请求(允许一定暂停)
  3. 低成本:更少内存、更少 CPU、同样吞吐(尤其容器/云上)

经验:线上多数问题是“延迟毛刺”或“内存不可控”,吞吐反而不是第一位。


2. 调优的基本套路(强烈建议照这个来)

2.1 先做基线(Baseline)

你需要一组“现在”的数据,才能证明“你改完更好了”。至少要有:

  • 业务指标:QPS、P99、错误率、超时率
  • JVM 指标:GC 次数/暂停、堆使用、线程数、类加载数
  • 系统指标:CPU、Load、RSS、IO、网络

2.2 再定位“瓶颈类型”

JVM 调优常见瓶颈几乎都落在这几类:

  • GC 压力大(分配太快 / 回收太慢 / 老年代膨胀)
  • 堆外内存吃满(DirectBuffer / mmap / Netty)
  • 元空间泄漏(类加载器泄漏、动态代理生成太多类)
  • 线程太多(栈内存、上下文切换、锁竞争)
  • CPU 火焰图热点(对象创建过多、序列化、正则、JSON、反射等)

2.3 再做小步实验(一次只改一个点)

  • 一次改一个参数或一个代码点
  • 压测/回放同样的流量
  • 用同样的指标对比(别凭感觉)

3. 先把“证据”拿到:你需要哪些观测手段

3.1 必开:GC 日志(别调优还不让 JVM 说话)

  • JDK8常用:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:/path/gc.log
  • JDK9+(推荐写法)
-Xlog:gc*,safepoint:file=/path/gc.log:time,uptime,tags,level

GC 日志是调优的“黑匣子”。没有它,很多结论都是拍脑袋。

3.2 线上常用命令(不重启也能看很多)

  • 查看 JVM 参数/版本/命令行:
jcmd<pid>VM.version jcmd<pid>VM.flags jcmd<pid>VM.command_line
  • 查看堆/类/线程概况:
jcmd<pid>GC.heap_info jcmd<pid>GC.class_stats jcmd<pid>Thread.print jstat -gcutil<pid>1000
  • 生成 dump(高危操作:注意磁盘、会 STW):
jcmd<pid>GC.heap_dump /tmp/heap.hprof jcmd<pid>VM.native_memory summary# 需要 -XX:NativeMemoryTracking=summary/detail

3.3 低开销性能分析(强烈推荐)

  • JFR(JDK11+ 非常好用):看分配热点、锁、线程、GC、IO
  • async-profiler:火焰图定位 CPU 热点/分配热点
  • Arthas:临时看热点方法、线程阻塞、Trace 调用链

4. JVM 里“真影响线上”的几个内存块(别只盯着堆)

  • Java Heap(堆):对象主要在这;你调的大部分参数都围绕它
  • Metaspace(元空间):类元数据;类加载器泄漏会炸它
  • Thread Stack(线程栈):线程多了就会占内存;-Xss影响
  • Direct/Native(堆外):Netty/NIO/ByteBuffer/mmap;OOM 不一定是堆 OOM
  • Code Cache:JIT 编译后的代码缓存,满了会退化性能

结论:你看到“机器内存快满了”,不代表堆满;也可能是堆外、线程、元空间。


5. 选择 GC:别迷信“最牛”,要看你的目标

5.1 常见选择(JDK 版本相关)

  • Parallel GC:吞吐强,暂停长(适合离线/批处理)
  • G1 GC:通用型,暂停可控(现代服务端默认主力)
  • ZGC / Shenandoah:超低暂停(延迟敏感、堆很大时很香,但看 JDK 版本支持)

如果你是常规 Spring Boot 微服务:优先 G1
如果你是低延迟且堆很大:考虑ZGC/Shenandoah(前提:JDK17+ 更稳)。


6. 调优最常见的“症状 -> 原因 -> 处理”

下面按线上最常见的几种痛点给出“排查路径 + 参数方向 + 代码方向”。


场景 A:Minor GC 很频繁,P99 抖动明显

症状

  • GC 日志里 Young GC(或 G1 的 Evacuation Pause)很密
  • CPU 有波动,吞吐受影响
  • jstat -gcutil看 YGC 次数飞涨

常见原因

  • 对象分配速率太高(JSON、字符串拼接、List/Map 频繁创建)
  • 新生代太小,撑不住分配洪峰
  • 大对象直接进老年代(G1 的 humongous)

处理步骤

  1. 先算“分配速率”
    • 用 JFR/async-profiler 看 Allocation flamegraph
  2. 业务上减分配:复用 buffer、避免临时对象、优化序列化
  3. 参数上:扩大可用堆/新生代(优先保证整体堆合理)
    • JDK8(CMS/Parallel)时代常见:-Xms -Xmx固定 +-XX:NewRatio
    • G1 时代不建议死抠 NewRatio,更建议给足堆、让 G1 自适应
  4. 如果是 humongous(大对象):
    • 优化大数组/大字符串/大 ByteBuffer 的生命周期
    • 让大对象别频繁创建(比如把大响应做流式输出)

场景 B:Full GC / Mixed GC 很慢,卡顿明显

症状

  • 请求出现“秒级/十秒级”暂停
  • GC 日志出现 Full GC 或 G1 Mixed Pause 很长
  • 老年代占用长期高位

常见原因

  • 老年代真的装不下了(泄漏或缓存不受控)
  • 晋升过快(Survivor 放不住,直接进老年代)
  • Reference(Soft/Weak/Phantom)处理开销大
  • G1:Region 回收跟不上 / Humongous 多

处理步骤

  1. 判断是“泄漏”还是“业务峰值
    • 堆使用曲线是否回不去?(回不去很像泄漏)
  2. Dump + 分析(MAT / YourKit / JProfiler)
    • 看 Dominator Tree、Top Consumers、GC Roots
  3. 如果不是泄漏:
    • 给足堆(最简单有效)
    • 调整 GC 目标(例如 G1 的-XX:MaxGCPauseMillis别设置得太激进)
  4. 如果是泄漏:
    • 修代码(缓存、静态集合、ThreadLocal、监听器、ClassLoader)
    • 绝大多数“调参数”救不了真正泄漏

场景 C:CPU 很高,但 GC 并不频繁

症状

  • CPU 常年 80%+,但 GC log 很平稳
  • QPS 上去后延迟爆炸

常见原因

  • 业务热点(序列化/反序列化、加解密、压缩、正则)
  • 锁竞争/线程切换(线程太多)
  • 线程池队列堆积导致上下游雪崩

处理步骤

  1. async-profiler 采 CPU 火焰图(最直接)
  2. 如果锁竞争:JFR 看 Monitor/Lock 事件
  3. JVM 参数通常不是主因,重点在:
    • 减少热点路径的对象创建与拷贝
    • 控制线程数,避免“堆线程解决一切”的幻觉
    • 让 IO 等待别变 CPU 忙等

场景 D:OOM 但堆没满(最容易误判)

典型报错

  • java.lang.OutOfMemoryError: Direct buffer memory
  • OutOfMemoryError: Metaspace
  • unable to create new native thread

处理思路

  • Direct OOM:看 Netty/NIO buffer;限制/监控-XX:MaxDirectMemorySize
  • Metaspace OOM:检查动态生成类/反射代理;设置-XX:MaxMetaspaceSize只是止血
  • Native thread:线程数太多或-Xss太大;减少线程、调小栈

建议开启 NMT(需要重启):

-XX:NativeMemoryTracking=summary

然后:

jcmd<pid>VM.native_memory summary

7. 一套“可抄作业”的调优检查清单

7.1 启动参数(通用基线)

  • 固定堆:减少动态扩容抖动
-Xms4g -Xmx4g
  • 记录崩溃信息、OOM 自动 dump(注意磁盘)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -XX:ErrorFile=/tmp/hs_err_pid%p.log
  • 打开 GC 日志(JDK9+ 推荐)
-Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags

7.2 容器/K8s 必看(很多人死在这里)

在容器里,别继续用“按物理机内存”的老思路,建议使用百分比参数(JDK10+ 更友好):

-XX:InitialRAMPercentage=50-XX:MaxRAMPercentage=75

重点:容器内存限制太小,堆 + 堆外 + 线程栈 + 元空间 = 一起把 Pod 干掉。


8. G1 常用调参方向(别上来就堆一堆 flags)

你真的需要调 G1 参数的情况:

  • 你已经有 GC 日志和明确瓶颈
  • 你已经确认“加堆/修分配”解决不了

几个常见方向:

  • 目标暂停时间(别设太小,不然 G1 会很激进、吞吐掉)
-XX:MaxGCPauseMillis=200
  • 触发 Mixed 回收阈值(让老年代别拖太久)
-XX:InitiatingHeapOccupancyPercent=30
  • 并行/并发线程(通常交给 JVM 自己,除非你 CPU 很小或很大)
-XX:ParallelGCThreads=8-XX:ConcGCThreads=2

经验:G1 最有效的“参数”经常不是参数,而是给足堆 + 降低分配率


9. 线上一次完整调优的“实战流程模板”

  1. 明确目标:比如 P99 < 200ms,Full GC 不能超过 200ms
  2. 开启/收集:GC 日志 + 指标(至少 1~3 天)
  3. 定位:
    • GC 问题?看暂停、频率、老年代趋势
    • CPU 问题?火焰图
    • 堆外问题?NMT/Direct 指标
  4. 提出假设:比如“分配率太高导致 YGC 频繁”
  5. 实验:
    • 先改代码(减少分配/减少大对象)
    • 再改参数(加堆、调 G1 阈值)
  6. 验证:压测/回放 + 对比基线
  7. 固化:把结论写进 runbook(别下次又从头踩坑)

10. 常见误区(踩了就会“越调越差”)

  • 只看堆,不看堆外/线程/元空间
  • GC 暂停长就疯狂调 MaxGCPauseMillis(会牺牲吞吐,甚至更抖)
  • 不做基线,改完只靠感觉
  • 一次改一堆参数,最后根本不知道哪个有效
  • 把泄漏当作“堆不够”(短期能活,长期必炸)
  • 容器里照搬物理机参数(OOMKilled + 还以为是 JVM 崩了)

11. 最小可用的“参数模板”(给你一个起点)

11.1 通用服务端(G1,JDK11/17)

-Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags

11.2 延迟敏感(ZGC,JDK17+,需要验证)

-Xms4g -Xmx4g -XX:+UseZGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Xlog:gc*,safepoint:file=/var/log/app/gc.log:time,uptime,level,tags

注意:不同 JDK 版本对 GC 实现和默认值差异很大,别跨版本硬抄 flags。


12. 快速自检:你现在到底该从哪里下手?

  • 你看到 P99 抖动 + GC 日志有长暂停→ 先看 GC 类型/暂停来源(Young/Mixed/Full)
  • 你看到内存涨到顶不回落→ 先怀疑泄漏(dump 分析),别先加堆
  • 你看到 CPU 常年高→ 火焰图先上,别先调 GC
  • 你看到 OOM 但堆不大→ 查 Direct/Metaspace/线程,别只看-Xmx

13. 附:常用命令速查

# 进程jps -l# GC/堆概况jstat -gcutil<pid>1000jcmd<pid>GC.heap_info# 线程栈jstack<pid>|head-200 jcmd<pid>Thread.print>/tmp/threaddump.txt# 导出堆 dump(可能 STW)jcmd<pid>GC.heap_dump /tmp/heap.hprof# NMT(需要启动时开 -XX:NativeMemoryTracking=summary)jcmd<pid>VM.native_memory summary

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 15:00:30

汽车EDI: Knorr-Bremse EDI 需求分析

Knorr-Bremse AG 是一家总部位于德国慕尼黑的全球领先工业企业&#xff0c;成立于 1905 年&#xff0c;主要专注于为 铁路车辆和商用车辆&#xff08;如卡车、公交车等&#xff09;制造制动系统及安全关键电子/机械系统。公司致力于提升道路和轨道交通的安全性、效率和可持续性…

作者头像 李华
网站建设 2026/1/29 13:11:11

LLaMA-Factory微调实战:从环境到训练全指南

LLaMA-Factory微调实战&#xff1a;从环境到训练全指南 在当前大模型技术飞速发展的背景下&#xff0c;如何将通用语言模型精准适配到具体业务场景&#xff0c;已成为开发者面临的核心挑战。尽管像 Llama、Qwen、Baichuan 等开源模型提供了强大的基础能力&#xff0c;但若未经定…

作者头像 李华
网站建设 2026/1/31 17:53:29

Excalidraw拖拽与缩放技术深度解析

Excalidraw拖拽与缩放技术深度解析 在现代协作型白板工具中&#xff0c;用户对交互流畅性的要求早已超越“能用”层面。当团队成员同时在一张无限画布上头脑风暴、调整架构图或绘制原型时&#xff0c;哪怕是一次轻微的卡顿、一次错位的拖动&#xff0c;都可能打断思维节奏。Exc…

作者头像 李华
网站建设 2026/1/29 12:37:17

实测3款论文降ai神器,手动+工具一键搞定降AIGC率!

最近毕业季&#xff0c;后台私信简直要炸了。很多同学都在哭诉&#xff1a;明明是自己一个字一个字码出来的论文&#xff0c;结果aigc降重检测结果竟然高达50%甚至70%以上。别慌&#xff0c;这其实是很多学生和研究者都会遇到的普遍问题。只要搞懂了原理&#xff0c;掌握正确的…

作者头像 李华
网站建设 2026/1/29 12:37:02

GNSS 形变监测系统:扼流圈 GNSS 监测站

提问&#xff1a;“北斗 GPS 双模定位 差分 RTK 技术”&#xff0c;具体精度能达到多少?对边坡、大坝监测来说意味着什么?​小助手支招&#xff1a;毫米级精准捕捉&#xff0c;隐患早发现早处置!系统通过北斗、GPS 多卫星系统融合定位&#xff0c;搭配差分 RTK 技术(基准站…

作者头像 李华
网站建设 2026/1/29 12:39:57

Java集合-Set讲解

目录一、集合框架层次结构二、Collection集合1、Set集合1、HashSet2、LinkedHashSet3、TreeSet4、ConcurrentSkipListSet5、CopyOnWriteArraySetJava 集合框架&#xff08;Collections Framework&#xff09;是 Java 中用于 存储和操作数据组的重要架构。它提供了一组接口、实现…

作者头像 李华