「过早优化是万恶之源」,但生产环境的 Full GC 停顿 5 秒、OOM 导致服务重启,就不是过早优化了——是必须解决的问题。本文以 Java 21 为基础,梳理 JVM 调优的系统方法。

先测量,再调优

没有指标就调参等于盲人摸象。上线前必须采集的基线数据:

推荐工具:async-profiler(CPU + 分配火焰图)、GCeasy(分析 GC 日志)、JFR(Java Flight Recorder,低开销持续采集)。

GC 选择:2026 年的答案

Java 21 提供四种主流 GC,选择策略:

  1. G1(默认):通用首选,停顿目标可控(-XX:MaxGCPauseMillis=200),适合堆 4–32 GB
  2. ZGC:停顿 < 1ms,适合大堆(> 32 GB)和低延迟服务。Java 21 已支持分代 ZGC
  3. Shenandoah:与 ZGC 类似,Red Hat 主导,OpenJDK 构建可用
  4. Parallel GC:吞吐优先的批处理场景,停顿不重要时用
# 低延迟 Web 服务推荐配置(Java 21 + 分代 ZGC)
java -XX:+UseZGC \
     -XX:+ZGenerational \
     -Xms4g -Xmx4g \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/heapdump.hprof \
     -Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags \
     -jar app.jar

内存泄漏排查流程

Old Gen 持续增长且 Full GC 后不回落,是典型的内存泄漏信号:

  1. 触发 jcmd <pid> GC.heap_dump 生成 heap dump
  2. 用 Eclipse MAT 或 VisualVM 分析 Dominator Tree
  3. 找到占用最大的对象链,定位持有引用的根因
  4. 常见元凶:静态集合只增不减、ThreadLocal 未清理、缓存无过期策略

堆外内存与直接内存

Netty、RocksDB 等库大量使用 DirectByteBuffer,这部分内存不在堆内,OOM 时堆 dump 看不出问题:

容器环境的 JVM 参数

在 Kubernetes 中运行 Java 应用需要注意:

调优禁忌

不要复制网上的「万能参数」——别人的 GC 日志和你的工作负载完全不同。

小结

JVM 调优的正确姿势:建立基线 → 定位瓶颈类型(CPU / 内存 / GC)→ 针对性调整 → 验证效果。90% 的场景下,选好 GC 算法 + 合理设置堆大小 + 修复内存泄漏,就足够了。