2026/1/8 4:48:31
网站建设
项目流程
在线教育自助网站建设平台,建立网站 域名 服务器,江苏神禹建设有限公司网站,wordpress adroidJVM 调优思路#xff08;从“瞎调参数”到“可验证的工程方法”#xff09;这份文档讲的是方法论 常见场景的落地步骤。JVM 调优不是背参数#xff0c;而是#xff1a;先测、再定位、再改、再验证。
默认以服务端 Java#xff08;Spring Boot / 微服务 / 容器#xff09…JVM 调优思路从“瞎调参数”到“可验证的工程方法”这份文档讲的是方法论 常见场景的落地步骤。JVM 调优不是背参数而是先测、再定位、再改、再验证。默认以服务端 JavaSpring Boot / 微服务 / 容器为主兼顾 JDK8JDK17 常见差异。1. 先把“调优目标”说清楚不然你永远在乱跑调优的目标通常就三类选一个当主目标别三心二意低延迟P99/P999 响应时间稳定GC 暂停可控高吞吐单位时间处理更多请求允许一定暂停低成本更少内存、更少 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.logJDK9推荐写法-Xlog:gc*,safepoint:file/path/gc.log:time,uptime,tags,levelGC 日志是调优的“黑匣子”。没有它很多结论都是拍脑袋。3.2 线上常用命令不重启也能看很多查看 JVM 参数/版本/命令行jcmdpidVM.version jcmdpidVM.flags jcmdpidVM.command_line查看堆/类/线程概况jcmdpidGC.heap_info jcmdpidGC.class_stats jcmdpidThread.print jstat -gcutilpid1000生成 dump高危操作注意磁盘、会 STWjcmdpidGC.heap_dump /tmp/heap.hprof jcmdpidVM.native_memory summary# 需要 -XX:NativeMemoryTrackingsummary/detail3.3 低开销性能分析强烈推荐JFRJDK11 非常好用看分配热点、锁、线程、GC、IOasync-profiler火焰图定位 CPU 热点/分配热点Arthas临时看热点方法、线程阻塞、Trace 调用链4. JVM 里“真影响线上”的几个内存块别只盯着堆Java Heap堆对象主要在这你调的大部分参数都围绕它Metaspace元空间类元数据类加载器泄漏会炸它Thread Stack线程栈线程多了就会占内存-Xss影响Direct/Native堆外Netty/NIO/ByteBuffer/mmapOOM 不一定是堆 OOMCode CacheJIT 编译后的代码缓存满了会退化性能结论你看到“机器内存快满了”不代表堆满也可能是堆外、线程、元空间。5. 选择 GC别迷信“最牛”要看你的目标5.1 常见选择JDK 版本相关Parallel GC吞吐强暂停长适合离线/批处理G1 GC通用型暂停可控现代服务端默认主力ZGC / Shenandoah超低暂停延迟敏感、堆很大时很香但看 JDK 版本支持如果你是常规 Spring Boot 微服务优先 G1。如果你是低延迟且堆很大考虑ZGC/Shenandoah前提JDK17 更稳。6. 调优最常见的“症状 - 原因 - 处理”下面按线上最常见的几种痛点给出“排查路径 参数方向 代码方向”。场景 AMinor GC 很频繁P99 抖动明显症状GC 日志里 Young GC或 G1 的 Evacuation Pause很密CPU 有波动吞吐受影响jstat -gcutil看 YGC 次数飞涨常见原因对象分配速率太高JSON、字符串拼接、List/Map 频繁创建新生代太小撑不住分配洪峰大对象直接进老年代G1 的 humongous处理步骤先算“分配速率”用 JFR/async-profiler 看 Allocation flamegraph业务上减分配复用 buffer、避免临时对象、优化序列化参数上扩大可用堆/新生代优先保证整体堆合理JDK8CMS/Parallel时代常见-Xms -Xmx固定 -XX:NewRatioG1 时代不建议死抠 NewRatio更建议给足堆、让 G1 自适应如果是 humongous大对象优化大数组/大字符串/大 ByteBuffer 的生命周期让大对象别频繁创建比如把大响应做流式输出场景 BFull GC / Mixed GC 很慢卡顿明显症状请求出现“秒级/十秒级”暂停GC 日志出现 Full GC 或 G1 Mixed Pause 很长老年代占用长期高位常见原因老年代真的装不下了泄漏或缓存不受控晋升过快Survivor 放不住直接进老年代ReferenceSoft/Weak/Phantom处理开销大G1Region 回收跟不上 / Humongous 多处理步骤判断是“泄漏”还是“业务峰值”堆使用曲线是否回不去回不去很像泄漏Dump 分析MAT / YourKit / JProfiler看 Dominator Tree、Top Consumers、GC Roots如果不是泄漏给足堆最简单有效调整 GC 目标例如 G1 的-XX:MaxGCPauseMillis别设置得太激进如果是泄漏修代码缓存、静态集合、ThreadLocal、监听器、ClassLoader绝大多数“调参数”救不了真正泄漏场景 CCPU 很高但 GC 并不频繁症状CPU 常年 80%但 GC log 很平稳QPS 上去后延迟爆炸常见原因业务热点序列化/反序列化、加解密、压缩、正则锁竞争/线程切换线程太多线程池队列堆积导致上下游雪崩处理步骤async-profiler 采 CPU 火焰图最直接如果锁竞争JFR 看 Monitor/Lock 事件JVM 参数通常不是主因重点在减少热点路径的对象创建与拷贝控制线程数避免“堆线程解决一切”的幻觉让 IO 等待别变 CPU 忙等场景 DOOM 但堆没满最容易误判典型报错java.lang.OutOfMemoryError: Direct buffer memoryOutOfMemoryError: Metaspaceunable to create new native thread处理思路Direct OOM看 Netty/NIO buffer限制/监控-XX:MaxDirectMemorySizeMetaspace OOM检查动态生成类/反射代理设置-XX:MaxMetaspaceSize只是止血Native thread线程数太多或-Xss太大减少线程、调小栈建议开启 NMT需要重启-XX:NativeMemoryTrackingsummary然后jcmdpidVM.native_memory summary7. 一套“可抄作业”的调优检查清单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,tags7.2 容器/K8s 必看很多人死在这里在容器里别继续用“按物理机内存”的老思路建议使用百分比参数JDK10 更友好-XX:InitialRAMPercentage50-XX:MaxRAMPercentage75重点容器内存限制太小堆 堆外 线程栈 元空间 一起把 Pod 干掉。8. G1 常用调参方向别上来就堆一堆 flags你真的需要调 G1 参数的情况你已经有 GC 日志和明确瓶颈你已经确认“加堆/修分配”解决不了几个常见方向目标暂停时间别设太小不然 G1 会很激进、吞吐掉-XX:MaxGCPauseMillis200触发 Mixed 回收阈值让老年代别拖太久-XX:InitiatingHeapOccupancyPercent30并行/并发线程通常交给 JVM 自己除非你 CPU 很小或很大-XX:ParallelGCThreads8-XX:ConcGCThreads2经验G1 最有效的“参数”经常不是参数而是给足堆 降低分配率。9. 线上一次完整调优的“实战流程模板”明确目标比如 P99 200msFull GC 不能超过 200ms开启/收集GC 日志 指标至少 13 天定位GC 问题看暂停、频率、老年代趋势CPU 问题火焰图堆外问题NMT/Direct 指标提出假设比如“分配率太高导致 YGC 频繁”实验先改代码减少分配/减少大对象再改参数加堆、调 G1 阈值验证压测/回放 对比基线固化把结论写进 runbook别下次又从头踩坑10. 常见误区踩了就会“越调越差”只看堆不看堆外/线程/元空间GC 暂停长就疯狂调 MaxGCPauseMillis会牺牲吞吐甚至更抖不做基线改完只靠感觉一次改一堆参数最后根本不知道哪个有效把泄漏当作“堆不够”短期能活长期必炸容器里照搬物理机参数OOMKilled 还以为是 JVM 崩了11. 最小可用的“参数模板”给你一个起点11.1 通用服务端G1JDK11/17-Xms4g -Xmx4g -XX:UseG1GC -XX:MaxGCPauseMillis200-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/tmp -Xlog:gc*,safepoint:file/var/log/app/gc.log:time,uptime,level,tags11.2 延迟敏感ZGCJDK17需要验证-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/线程别只看-Xmx13. 附常用命令速查# 进程jps -l# GC/堆概况jstat -gcutilpid1000jcmdpidGC.heap_info# 线程栈jstackpid|head-200 jcmdpidThread.print/tmp/threaddump.txt# 导出堆 dump可能 STWjcmdpidGC.heap_dump /tmp/heap.hprof# NMT需要启动时开 -XX:NativeMemoryTrackingsummaryjcmdpidVM.native_memory summary