JVM调优实战
GC 类型(Young/Mixed/Full)、暂停时间、堆前后使用量、晋升/回收速率、触发原因(Allocation Failure / Humongous Allocation / Metadata GC Threshold 等)、并发周期细节(G1/ZGC)。:Young/Mixed/Old/Full 次数与耗时、晋升失败(promotion failed)、Humongous 分配(G1)
目录
调优前的共识与边界
-
调优不是魔法:优先做定位与定量,明确是吞吐问题(QPS/TPS)还是延迟问题(P99/P999),还是资源问题(内存/OOM)。
-
目标要量化:例如“P99 < 80ms、单节点 QPS ≥ 5k、Full GC 平均间隔 ≥ 6h”。
-
先基线、后改动:留住原始指标与 GC 日志,所有更改都走 A/B 对照 + 回滚预案。
-
80/20 法则:通常 20% 的热点对象、接口或代码路径决定 80% 的收益。
JVM 基础速通:你只需明确的那点事
-
堆(Heap):新生代(Eden/Survivor)+ 老年代。对象大多先在 Eden,幸存后晋升老年代。
-
元空间(Metaspace):类元数据、常量池、JIT 代码缓存等。
-
GC 触发:年轻代满了 → Young GC;老年代触发阈值、混合回收或空间不足 → Mixed/Old/Full GC。
-
STW(Stop-The-World):多数阶段会暂停应用线程。调优的本质是减少暂停频率与时长,或将其并发化。
关键指标体系:如何“量化”好坏
-
延迟:平均、P95、P99、P999。
-
吞吐:QPS/TPS、CPU 利用率。
-
GC 指标:Young/Mixed/Old/Full 次数与耗时、晋升失败(promotion failed)、Humongous 分配(G1)、回收比例(rebalance/并发周期)。
-
内存健康:堆使用率曲线、对象晋升速率、分配速率、Metaspace 使用、Direct 内存使用。
-
JIT/锁:编译热度、OSR 触发、锁竞争/膨胀、Safepoint 统计。
工具箱与开箱即用命令
JDK 自带
-
jcmd <pid> VM.flags | VM.system_properties | GC.heap_info | GC.class_histogram
-
jcmd <pid> GC.heap_dump filename=/tmp/heap.hprof
-
jcmd <pid> JFR.start name=profile settings=profile filename=/tmp/app.jfr duration=120s
-
jstat -gcutil <pid> 1s 30
(快照 GC 百分位) -
jmap -histo:live <pid>
(活对象直方图) -
jstack <pid>
(线程栈)
常用三方
-
async-profiler
(CPU/Wall/Alloc/Lock 火焰图),./profiler.sh -e cpu -d 60 -f /tmp/cpu.html <pid>
-
Arthas(线上排障),VisualVM/JMC(图形分析)
GC 日志与统一日志(UL)快速解读
JDK 8(推荐):
bash
复制编辑
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime \ -Xloggc:/var/log/app/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=50M
JDK 11+(统一日志):
bash
复制编辑
-Xlog:gc*,safepoint*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=50M
看点:
-
GC 类型(Young/Mixed/Full)、暂停时间、堆前后使用量、晋升/回收速率、触发原因(Allocation Failure / Humongous Allocation / Metadata GC Threshold 等)、并发周期细节(G1/ZGC)。
收集器选型与参数心法(G1/ZGC 为主)
G1(JDK11+ 默认):延迟可控,适合中大内存(几 GB~数十 GB),暂停时间可设置。
-
目标:
-XX:MaxGCPauseMillis=200
(从 200ms 起步按需调整) -
混合回收触发:
-XX:InitiatingHeapOccupancyPercent=45
(默认 45,可按实际堆增速调优) -
Region 大小:自动即可(1~32MB),极端场景可用
-XX:G1HeapRegionSize=...
-
Humongous 对象问题多见(> 50% Region 大小的对象)。规避:减少大对象、压缩对象、池化。
ZGC(JDK15+ 稳定):超低停顿、超大堆、并发标记整理,参数更少。
-
吞吐略低但延迟极稳(STW 低至亚毫秒~数毫秒级)。
-
重点保证内存余量与合理堆大小;关注
-XX:ZUncommitDelay
、NUMA、透明大页等。
简单选型:
-
延迟敏感(P99 强约束)、大堆 → ZGC。
-
综合平衡、中大堆、稳定生态 → G1。
-
CMS 已废弃,不建议新项目使用;Parallel 适合批处理型吞吐场景。
内存布局与常用参数调优路径
基础参数模板(G1 示例):
bash
复制编辑
-Xms8g -Xmx8g # 固定堆,减少扩缩容抖动 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:+ParallelRefProcEnabled # 并行处理引用,加快 GC -XX:+AlwaysPreTouch # 预触摸页,消除首次分配抖动(内存充足时用) -XX:MaxTenuringThreshold=8 # 幸存晋升阈值,需结合实际年轻代回收效果 -XX:+UseStringDeduplication # 字符串去重(G1 专属) -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=1g
新生代大小与 Survivor 比例:
-
-XX:NewRatio
或-XX:MaxNewSize/-XX:NewSize
(G1 一般交给自动调优); -
-XX:SurvivorRatio
与-XX:TargetSurvivorRatio
决定幸存区压力与晋升速率。
触发点:
-
Young 过于频繁 → 增大新生代或优化分配热点。
-
Mixed 过久/回收不动 → 检查大对象、老年代碎片、IHOP;提高并发标记效率或放宽暂停目标。
热点问题定位套路:从现象到根因
-
确认问题类型:延迟突刺 / 吞吐不达标 / OOM / Full GC 频繁。
-
收集证据:GC 日志、JFR/火焰图、
jstat -gcutil
、jcmd GC.heap_info
、系统与容器监控。 -
锁定维度:
-
分配速率与对象寿命(TLAB、短命/长命对象)
-
老年代增长来源(晋升/直接分配/大对象)
-
Safepoint 统计(偏向锁撤销、Class Unloading、代码缓存满)
-
-
提出假设 → 小步改动 → A/B 对照 → 回归报告。
三个实战案例:可复用的诊断脚本与调整建议
案例一:Full GC 频繁(晋升失败/老年代碎片)
现象:业务无明显峰值,但每 10~30 分钟一次 Full GC,P99 抖动大。
诊断步骤:
-
看 GC 日志触发原因:
to-space exhausted
、promotion failed
、Humongous
频繁。 -
jcmd <pid> GC.class_histogram
查看大对象/热对象类型;JFR 看分配热点方法。
优化方向: -
G1:放宽
MaxGCPauseMillis
(如 200→300),让 Mixed 回收更多;适当降低InitiatingHeapOccupancyPercent
(如 45→35),更早开启并发标记;排查 Humongous:对象分片、使用池化或压缩数据结构。 -
降低短生命周期大对象的产生(如频繁拼接大字符串、临时大集合)。
案例二:延迟尖刺(Safepoint / 偏向锁撤销 / Humongous 分配)
现象:P999 偶发飙高。
诊断:
-
开
-Xlog:safepoint
(JDK11+)或-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
(JDK8) -
JFR 事件看
BiasedLockingRevocation
(JDK15 前)、ThreadPark
、MonitorEnter
。
优化: -
JDK11 仍在用偏向锁且撤销频繁:
-XX:-UseBiasedLocking
(JDK15 起已移除偏向锁)。 -
减少巨大临时对象(避免触发 Humongous 分配)。
-
降低 GC 暂停目标(更长暂停预算可减少频繁调度)。
案例三:内存泄漏(ThreadLocal/缓存/类加载器)
现象:堆使用锯齿上升但不回落,最终 OOM。
诊断:
-
Heap dump(活对象):
jcmd <pid> GC.heap_dump filename=/tmp/heap.hprof
,用 MAT/JMC 分析 Dominator Tree。 -
关注
ThreadLocalMap
、自建缓存、未关闭的ClassLoader
(热部署/插件)。
优化: -
正确清理 ThreadLocal(
try/finally remove()
),缓存加 TTL/基于访问淘汰。 -
类加载泄漏:确保
URLClassLoader
可回收、避免对Class
的静态强引用。
JIT、锁与逃逸分析:吞吐与延迟的幕后推手
-
C1/C2 编译:热点方法被优化内联、标量替换。
-
逃逸分析:对象不逃逸可栈上分配,减少 GC 压力(受代码形态影响)。
-
锁优化:减少共享写、使用
LongAdder
替代AtomicLong
,细化锁粒度,使用无锁/分段结构;避免在高频路径上创建大临时对象引发竞争。 -
观测:JFR 看
Compiler
、CodeCache
;async-profiler 看lock
与wall
事件。
类加载与元空间:不常出事但一出就大
-
关注
-XX:MaxMetaspaceSize
与-Xlog:metaspace*
。 -
动态加载/热部署场景避免类加载器被上层静态引用。
-
代码缓存满(CodeCache Full)会导致编译失败、性能骤降,监控
-Xlog:codecache*
,必要时增大-XX:ReservedCodeCacheSize=512m
。
I/O 与堆外内存:DirectBuffer、Netty 与 GC 的“互相伤害”
-
Direct 内存通过 Cleaner 回收,不等同于 GC 周期,出现“堆很空但 OOM(Direct)”很常见。
-
限制与观测:
-XX:MaxDirectMemorySize=2g
,结合 Netty Pooled ByteBuf,监控进/出站积压。 -
大量零拷贝/磁盘缓存与 Page Cache 相互作用,注意系统层面的监控(cgroup 限制、压力下回收)。
容器与云原生环境的特别注意
-
使用容器:确保 UseContainerSupport(JDK10+ 默认)识别 cgroup 限制。
-
避免 Xms/Xmx 超过容器可用内存,留出 OS 与堆外余量(建议总内存的 60%~75% 给堆)。
-
规避 OOMKill:结合
-XX:+ExitOnOutOfMemoryError
做出快速失败与自愈。 -
预触摸
-XX:+AlwaysPreTouch
在容器冷启动时间与内存占用之间取舍。
压测与回归:如何证明你真的“变快了”
-
微基准:JMH(谨慎理解结果,避免误读死循环优化、逃逸分析影响)。
-
系统压测:生产一致数据/拓扑/限流,打桩采集 GC、CPU、分配速率、P99/P999。
-
回归标准:调参前后 同负载 比较;至少覆盖 峰值 1.2× 压力做稳态观测 ≥ 30 分钟。
调优决策树与最终清单
从现象入手
-
延迟尖刺 → 看 Safepoint/锁/大对象 → 放宽 GC 暂停目标或减少 Humongous → JFR/火焰图定位。
-
吞吐不足 → 火焰图找热点 → JIT 命中与内联情况 → 数据结构与锁竞争优化。
-
Full GC 多 → 晋升失败/碎片/元空间/Direct → 调整 IHOP、暂停目标、对象形态。
-
OOM → 区分堆/元空间/Direct → HeapDump/JFR/Netty 池化/泄漏排查。
落地清单
-
开启 统一 GC 日志 与 Safepoint 日志。
-
固定堆大小(Xms=Xmx),先用 G1 基线。
-
设置暂停目标(200ms 起),观察 24h。
-
周期性 JFR(短时采样 60~120s)+ async-profiler(CPU/Alloc)。
-
A/B 对照与回滚脚本准备。
-
记录“参数→指标”映射,形成服务级“黄金配置”。
附录:常用命令速查与参数模板
1) 线上排障最小集
bash
复制编辑
# 观察 GC 百分比 jstat -gcutil <pid> 1s 60 # 堆/类直方图 jcmd <pid> GC.class_histogram # 堆转储(可能会卡:在低峰操作) jcmd <pid> GC.heap_dump filename=/tmp/heap.hprof # 线程与死锁 jstack -l <pid> > /tmp/jstack.txt # 启动 JFR 采样 2 分钟 jcmd <pid> JFR.start name=profile settings=profile filename=/tmp/app.jfr duration=120s
2) G1 参数模板(延迟优先)
bash
复制编辑
-Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:+AlwaysPreTouch -XX:MaxTenuringThreshold=8 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=1g -Xlog:gc*,safepoint*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=50M
3) ZGC 参数模板(超低停顿)
bash
复制编辑
-Xms16g -Xmx16g -XX:+UseZGC -XX:+ZGenerational # JDK21+ 可用的分代 ZGC -Xlog:gc*,safepoint*:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=50M
4) 容器建议
```bas
# 容器可用 16g(示例),给堆 10~12g,留 Direct/OS 余量 -Xms12g -Xmx12g -XX:+ExitOnOutOfMemoryError
小结
-
先度量再调参:日志 + 火焰图 + JFR 才是实锤。
-
优先处理数据结构与分配形态,再谈 GC 参数。
-
参数有通用起点,但没有放之四海皆准的“圣杯”——你的业务负载才是最终答案。
bash
复制编辑
更多推荐
所有评论(0)