目录

  1. 调优前的共识与边界

  2. JVM 基础速通:你只需明确的那点事

  3. 关键指标体系:如何“量化”好坏

  4. 工具箱与开箱即用命令

  5. GC 日志与统一日志(UL)快速解读

  6. 收集器选型与参数心法(G1/ZGC 为主)

  7. 内存布局与常用参数调优路径

  8. 热点问题定位套路:从现象到根因

  9. 三个实战案例:可复用的诊断脚本与调整建议

  10. JIT、锁与逃逸分析:吞吐与延迟的幕后推手

  11. 类加载与元空间:不常出事但一出就大

  12. I/O 与堆外内存:DirectBuffer、Netty 与 GC 的“互相伤害”

  13. 容器与云原生环境的特别注意

  14. 压测与回归:如何证明你真的“变快了”

  15. 调优决策树与最终清单

  16. 附录:常用命令速查与参数模板


调优前的共识与边界

  • 调优不是魔法:优先做定位与定量,明确是吞吐问题(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;提高并发标记效率或放宽暂停目标。


热点问题定位套路:从现象到根因

  1. 确认问题类型:延迟突刺 / 吞吐不达标 / OOM / Full GC 频繁。

  2. 收集证据:GC 日志、JFR/火焰图、jstat -gcutiljcmd GC.heap_info、系统与容器监控。

  3. 锁定维度

    • 分配速率与对象寿命(TLAB、短命/长命对象)

    • 老年代增长来源(晋升/直接分配/大对象)

    • Safepoint 统计(偏向锁撤销、Class Unloading、代码缓存满)

  4. 提出假设小步改动A/B 对照回归报告


三个实战案例:可复用的诊断脚本与调整建议

案例一:Full GC 频繁(晋升失败/老年代碎片)

现象:业务无明显峰值,但每 10~30 分钟一次 Full GC,P99 抖动大。
诊断步骤

  • 看 GC 日志触发原因:to-space exhaustedpromotion failedHumongous 频繁。

  • 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 前)、ThreadParkMonitorEnter
    优化

  • 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 看 CompilerCodeCache;async-profiler 看 lockwall 事件。


类加载与元空间:不常出事但一出就大

  • 关注 -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 池化/泄漏排查。

落地清单

  1. 开启 统一 GC 日志Safepoint 日志。

  2. 固定堆大小(Xms=Xmx),先用 G1 基线。

  3. 设置暂停目标(200ms 起),观察 24h。

  4. 周期性 JFR(短时采样 60~120s)+ async-profiler(CPU/Alloc)。

  5. A/B 对照与回滚脚本准备。

  6. 记录“参数→指标”映射,形成服务级“黄金配置”。


附录:常用命令速查与参数模板

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

复制编辑

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐