news 2026/4/23 12:35:11

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/4/23 12:30:28

AIGC检测免费网站哪个靠谱?2025年权威测评与选择指南

在人工智能生成内容&#xff08;AIGC&#xff09;迅猛发展的当下&#xff0c;如何有效鉴别文本是否由AI生成&#xff0c;已成为教育、出版、内容创作等领域面临的迫切问题。众多免费AIGC检测网站应运而生&#xff0c;但其检测能力、可靠性和适用场景各不相同&#xff0c;让用户…

作者头像 李华
网站建设 2026/4/22 16:14:37

线性代数(七)主变量与特解

本篇主要讨论如何求解齐次方程组的解&#xff0c;即求解 举例&#xff0c;&#xff0c;首先容易想到的算法是通过初等行变换进行消元&#xff0c;将其化为行最简形式。这里指出&#xff0c;在进行初等变换的时候&#xff0c;解构成的空间是不会改变的。 因为&#xff0c;对系…

作者头像 李华
网站建设 2026/4/23 8:21:11

Seelen UI:专业级UI/UX设计工具,助力高效界面设计与团队协作

Seelen UI是一款功能全面的用户界面设计与原型制作工具&#xff0c;专为设计师、开发人员和产品经理打造。该软件通过直观的操作界面和强大的设计功能&#xff0c;为用户提供了从概念设计到交互原型的一站式解决方案&#xff0c;显著提升界面设计效率。 获取地址&#xff1a;h…

作者头像 李华
网站建设 2026/4/23 8:20:17

Playwright03-CDP/WebSocket/PlayWright

Playwright03-CDP/WebSocket/PlayWrightplaywright自动化开发记录&#xff0c;学习BrowserUse的时候涉及到playwright知识点1-CDP/WebSocket/PlayWright对比我在看源码的时候&#xff0c;最开始使用简单demo进行学习的时候还好&#xff0c;但是一复杂起来&#xff0c;我就理不清…

作者头像 李华
网站建设 2026/4/22 13:06:51

Excalidraw使用技巧:从数据到图表的高效转化

Excalidraw使用技巧&#xff1a;从数据到图表的高效转化 在产品设计与技术协作中&#xff0c;最耗时的往往不是思考本身&#xff0c;而是把脑子里的想法“画出来”。你有没有过这样的经历&#xff1a;会议中刚理清一个系统流程&#xff0c;却因为要手动拖拽十几个方框、连线、…

作者头像 李华
网站建设 2026/4/23 8:17:24

FLUX.1-dev模型本地训练与推理指南(GPU/NPU)

FLUX.1-dev模型本地训练与推理指南&#xff08;GPU/NPU&#xff09; 模型简介 FLUX.1-dev 是由 Black Forest Labs 推出的下一代文生图多模态大模型&#xff0c;作为 Stable Diffusion 原班团队的新作&#xff0c;其在生成式人工智能领域树立了新的技术标杆。该模型基于创新的…

作者头像 李华