Java JVM 调优系统学习手册(JDK 8111721 通吃版)

内容纲要

标签:Java, JVM, GC, 性能优化, G1, ZGC, Shenandoah, JDK8, JDK11, JDK17, JDK21, Kubernetes, 容器化, JFR, async-profiler

目标:一份从入门到精通的 JVM 性能与稳定性实战手册,覆盖主流 LTS 版本(8/11/17/21),给出“可复制”的调优方法、参数模板、问题排查 SOP,与典型场景配方。看完即可上手,遇事有章可循。


0. 学会先“调思维”,再“调参数”

金律

  1. 先度量→再假设→小步调整→复测回归;
  2. 明确目标:吞吐(QPS/批处理速度)、延迟(p95/p99)、资源占用(内存/CPU/成本)、启动/冷启动
  3. 记住:绝大多数“JVM问题”是“系统问题”(I/O、线程池、数据库、网络、容器限额),JVM 只是放大镜。

常见误区

  • 只改 GC 参数不看 GC 日志/火焰图;
  • 只看平均延迟不看 p99;
  • 容器里忘了 cgroups 导致堆外挤爆;
  • 误开对象池/字符串池,反而制造 GC 压力。

1. 一张图看懂 JVM(8/11/17/21)

运行时构成

  • Java 堆:年轻代(Eden + Survivor)/ 老年代(或分区化,如 G1 分区)。
  • MetaSpace(8+,替代 PermGen)
  • Code Cache(JIT 编译后的机器码)
  • 线程栈(-Xss)
  • 堆外内存(直接缓冲区、JNI、NIO、ZGC/Shenandoah 元数据等)

GC 矩阵

  • JDK 8:Parallel(默认)、CMS(老牌低停顿,后续移除)、G1(可选,8u20+ 支持 String Dedup)。
  • JDK 11:G1(默认),ZGC(实验→15 起转正),Shenandoah(OpenJDK 较早可用),Epsilon(无回收)。
  • JDK 17:G1(默认),ZGC(稳定)Shenandoah(稳定)
  • JDK 21:G1(默认),ZGC 代际化(Generational ZGC),Shenandoah(含代际变体),虚拟线程(Loom)进入 GA,JIT 与 CDS 更完善。

日志与容器

  • JDK 8:旧 GC 日志(-XX:+PrintGCDetails -Xloggc)。容器感知需 8u191+ 搭配 -XX:+UseCGroupMemoryLimitForHeap(早期)或使用百分比/分数参数。
  • JDK 9+:统一日志(-Xlog:gc*),-XX:MaxRAMPercentage 等百分比参数,cgroups v2 支持完善(14+)。

2. 选择 GC 的“决策树”(实战口径)

  • 延迟优先(p99 < 50ms 或更苛刻)
    • JDK 17/21:首选 ZGC(大堆/超低停顿)或 Shenandoah(低停顿、易调)。
    • JDK 8/11G1(设 MaxGCPauseMillis=50~200ms),避免 CMS 新建项目。
  • 吞吐优先(批处理/离线计算)
    • Parallel GC(8/11/17/21 均可),多核充分,停顿可接受。
  • 通用在线服务(微服务)
    • JDK 11+G1 默认足够好;堆较大、延迟敏感可上 ZGC(17/21)。
  • 超大堆(> 32 GB)/ 极端低抖动
    • ZGC(17/21) 优先。

3. 可靠的内存配额与容量规划(含容器/K8s)

3.1 统一口径(经验安全线)

Pod/容器 Memory 限额 = L
预留(MetaSpace + 代码缓存 + 线程栈 + 直接内存 + GC/其他)= R(建议 25% × L,至少 512 MiB)
建议 -Xmx = L - R,并 -Xms = -Xmx 保持稳定

细项估算

  • 线程栈:线程数 × Xss(默认 ~1 MB/线程,虚拟线程不占用传统 1:1 栈,但仍有调度开销)
  • 直接内存(Netty 等):显式设置 -XX:MaxDirectMemorySize,否则可能过度分配
  • MetaSpace:典型 100–512 MiB(取决于类数量/动态代理)
  • 代码缓存:64–256 MiB(视 JIT 活跃度)

容器标配(JDK 11+)

-XX:InitialRAMPercentage=50 \
-XX:MaxRAMPercentage=70 \          # 70% 给堆
-XX:MinRAMPercentage=50 \
-XX:MaxDirectMemorySize=512m \     # Netty/DirectBuffer 显式上限
-XX:+AlwaysPreTouch               # 大堆避免运行时缺页抖动(仅内存足够时)

JDK 8(老项目)

  • 8u191+ 推荐:-XX:MaxRAMFraction=1 -XX:InitialRAMFraction=2 或显式 -Xmx/-Xms
  • 早期容器需:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap(仅在旧 8u 上)。

4. 版本关键差异与“坑位图”

  • Biased Locking:15 起默认禁用,18 起删除 → 不再依赖相关开关。
  • Compact Strings(9+):自动按 Latin-1/UTF-16 存储,提高字符串密度。
  • String Dedup(G1 专属)-XX:+UseStringDeduplication(8u20+,9+ 继续有效),换 CPU 降 GC 压力。
  • CDS/AppCDS:11+ 开源易用;21 上生成与加载更顺滑,显著提升启动/冷启动。
  • 统一日志(9+):一套 -Xlog: 覆盖 GC/安全点/类加载等。
  • ZGC/Shenandoah:11 起可用,15/17 转正更稳;21 引入代际 ZGC,改善短命对象吞吐与内存回收效率。
  • 虚拟线程(21):海量并发编程模型改变线程调优方式(详见 §10)。

5. GC 目标与关键开关(按 GC 策略)

5.1 G1(推荐通用、低风险)

常用:

-XX:+UseG1GC
-XX:MaxGCPauseMillis=100            # 50~200 之间按 SLA 设
-XX:InitiatingHeapOccupancyPercent=30  # 触发并发标记的阈值
-XX:G1ReservePercent=20
-XX:ParallelGCThreads=<CPU>         # 一般自动;容器 CPU 低时可显式
-XX:ConcGCThreads=<CPU/4..CPU/2>
-XX:+UnlockDiagnosticVMOptions -XX:+G1SummarizeRSetStats -XX:+G1SummarizeConcMark
# 字符串去重
-XX:+UseStringDeduplication

5.2 ZGC(低停顿/大堆/21 代际)

-XX:+UseZGC
# 代际(21)
# 默认已代际化,如需软上限:
-XX:SoftMaxHeapSize=<N>      # 动态控制堆扩张倾向
# 可配:
-XX:ZCollectionInterval=...  # 最大间隔触发
-XX:ZUncommitDelay=...       # 空闲内存回退

一般不需要也不建议对 ZGC 做复杂“手调”,观测 + 负载对齐更重要。

5.3 Shenandoah(低停顿、易用)

-XX:+UseShenandoahGC
# 代际变体(视发行版/版本)
-XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational

5.4 Parallel(吞吐优先/批处理)

-XX:+UseParallelGC
-XX:MaxGCPauseMillis=200~500
-XX:ParallelGCThreads=<CPU>

6. GC 日志与可观测性(8 vs 9+)

6.1 JDK 8

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause \
-Xloggc:/logs/gc.log \
-XX:+PrintGCApplicationStoppedTime \
-XX:+PrintTenuringDistribution \
-XX:+PrintAdaptiveSizePolicy \
-XX:+PrintGCID

6.2 JDK 9+

-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags
# 常用子系统
-Xlog:safepoint=info:stringtable=info:class+load=info,class+unload=info

怎么看?

  • 关注 STW 时间(GC/安全点),晋升失败/并发失败混合回收时长回收比率浮动垃圾
  • 观察 Young/Old 分配速率晋升速率 是否匹配对象存活特征;
  • 趋势 为主,结合业务流量曲线。

7. 标准工具箱(命令速查)

线上“十分钟”取证清单

# 1) 进程/资源
top -H -p <pid>                # 线程占用
pmap -x <pid> | tail           # 虚拟内存分布
cat /sys/fs/cgroup/...         # 容器限额核查

# 2) 线程/栈
jcmd <pid> Thread.print
jstack -l <pid> > threads.txt

# 3) 堆/类
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram > histo.txt
jmap -histo <pid> | head
jcmd <pid> VM.native_memory summary  # NMT 需 -XX:NativeMemoryTracking=summary/detail

# 4) 即刻开 JFR(11+)
jcmd <pid> JFR.start name=onfly settings=profile duration=120s filename=/tmp/onfly.jfr
# 或停止/导出
jcmd <pid> JFR.dump name=onfly filename=/tmp/onfly.jfr
jcmd <pid> JFR.stop name=onfly

# 5) 导出堆(大文件注意磁盘)
jmap -dump:live,file=/tmp/heap.hprof <pid>

火焰图(Linux,生产通用)

  • async-profiler(CPU/Wall/分配/锁),命令示例:
    ./profiler.sh -e cpu -d 60 -f out.svg <pid>

8. 典型场景“黄金模板”(可直接套用)

8.1 延迟敏感 API(p99 ≤ 100ms),JDK 17/21 + G1

-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:InitiatingHeapOccupancyPercent=30
-XX:G1ReservePercent=20
-XX:+UseStringDeduplication
-XX:+ParallelRefProcEnabled
-Xms=4g -Xmx=4g                        # 与容器配额联动
-XX:MaxRAMPercentage=70
-XX:MaxDirectMemorySize=512m
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags
-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps

8.2 极低停顿(金融撮合/交易风控),JDK 21 + ZGC

-XX:+UseZGC
-XX:SoftMaxHeapSize=8g
-Xms=8g -Xmx=8g
-XX:MaxRAMPercentage=70
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags
-XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError

8.3 批处理/离线任务(吞吐优先),JDK 11/17 + Parallel

-XX:+UseParallelGC
-XX:MaxGCPauseMillis=400
-Xms=16g -Xmx=16g
-XX:ParallelGCThreads=<CPU>
-Xlog:gc*:file=/logs/gc.log:time,uptime

8.4 Netty/网关(直接内存高),JDK 17 + G1

-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:InitiatingHeapOccupancyPercent=30
-Xms=4g -Xmx=4g
-XX:MaxDirectMemorySize=1g              # 显式限制 DirectBuffer
-XX:+DisableExplicitGC                  # 避免误调 System.gc()
-Dio.netty.leakDetection.level=paranoid # 漏洞排查期

8.5 容器/微服务(通用基线),JDK 11/17

-XX:+UseG1GC
-XX:InitialRAMPercentage=50
-XX:MaxRAMPercentage=70
-XX:MinRAMPercentage=50
-XX:MaxDirectMemorySize=256m
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags

9. 稳定性“保险丝”

  • -XX:+ExitOnOutOfMemoryError:OOM 直接退出,避免“假活着”;
  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps:留证;
  • -XX:NativeMemoryTracking=summary:堆外排查(需开销,可按需打开);
  • 限速与熔断:线程池/队列/连接池上限明确;
  • 显式 MaxDirectMemorySize:避免 Netty 类库默认“想用多少用多少”。

10. 代码与 JIT 调优要点

  • 逃逸分析 & 标量替换:让对象“栈上分配”。减少同步与分配压力(避免新建临时大对象)。
  • TLAB:线程本地分配缓冲,默认即可;极端场景再看 -XX:-ResizeTLAB
  • 内联/去优化:过深层级影响 JIT,关注火焰图的 vtable 调用与 megamorphic call。
  • 并发工具:热点整数累加用 LongAdder;注意 @Contended(避免伪共享,8 需 -XX:-RestrictContended)。
  • 字符串:9+ 已 Compact Strings,一般无需手工“池化”;G1 可开 String Dedup
  • 对象池:现代 JVM 下多数对象池是“反模式”(连接池除外)。
  • 虚拟线程(JDK 21)
    • 大幅提升阻塞型 I/O 并发能力;
    • 避免同步阻塞把载体线程(carrier)“钉死”(如 synchronized 长持有/本地调用阻塞),尽量采用非阻塞 I/O;
    • 线程池调参转为限制请求层面并发(信号量/背压),减少手动池大小微调。

11. JFR/火焰图排查“SOP”

当你遇到 p99 抖动 / CPU 飙升 / GC 频繁 / OOM 先兆:

  1. 采样
    • jcmd JFR.start settings=profile duration=120s filename=/tmp/now.jfr
    • 同时跑 async-profiler./profiler.sh -e cpu -d 60 -f cpu.svg <pid>
  2. 看图
    • 热点在用户代码?库?系统调用?锁争用还是分配风暴?
    • 分配火焰图若在某模型/DTO 构造 → 减少短命对象/复用缓冲。
  3. 对症
    • 分配过高 → G1/ZGC 均会更勤快;优先从代码/缓存降压;
    • 锁热点 → 优化粒度/使用无锁容器/减少共享写;
    • I/O 等待 → 线程爆炸?切到虚拟线程或改善下游吞吐。

12. 常见疑难杂症与对策

症状 观察 快速定位 处理建议
Heap OOM GC 日志晋升失败/Full GC 后仍满 jmap -histo, JFR 对象存活 修复泄漏、降缓存上限、增堆、限并发
MetaSpace OOM Metaspace 增长不回落 jcmd VM.native_memory detail, classloader 统计 减少动态代理/热加载,限制插件,升级框架
频繁 Young GC 分配速率过高 GC 日志分配速率,JFR Allocation 降对象 churn,调小 MaxGCPauseMillis 并观察
老年代膨胀 存活对象多、晋升快 PrintTenuringDistribution/JFR 提高 Survivor,优化生命周期,调 IHOP(G1)
STW 突刺 safepoint 过长 -Xlog:safepoint 避免大规模类卸载、缩小 CodeCache 抖动、定位慢操作
Native OOM / 被 OOMKiller cgroup 限额+堆外膨胀 NMT/sys/fs/cgroup 显式 MaxDirectMemorySize,减线程栈,留足 R
直接内存泄漏 Netty 报 warn/GC 正常 Netty LeakDetector 升级/修复引用/确保 release()

13. 启动/冷启动优化(CDS / AppCDS)

快速做法(JDK 11/17/21)

  1. 生成 CDS:java -Xshare:dump(基础)
  2. AppCDS:使用 JFR 录制类列表 → jcmd VM.classloaders/工具导出,生成自定义归档;
  3. 运行:-Xshare:on,显著缩短启动与暖机时间(微服务冷启收益明显)。

14. 线程池与并发治理(线上可靠性)

  • 所有线程池 有界 + 拒绝策略 明确(记录/回退/降级);
  • 拆分 CPU 密集/IO 密集池,避免互相饥饿;
  • 虚拟线程时代:不再用巨大固定池,转为按请求限流 + 背压

15. 参数“口袋书”(JDK 分版本速查)

15.1 JDK 8(常见)

# GC
-XX:+UseG1GC | -XX:+UseParallelGC | -XX:+UseConcMarkSweepGC(老项目)
-XX:+UseStringDeduplication  # 仅 G1

# 堆与容器
-Xms/-Xmx 或 -XX:MaxRAMFraction / InitialRAMFraction
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (非常旧的 8u)

# 日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log

# 诊断
-XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError

15.2 JDK 11/17/21(统一)

# GC
-XX:+UseG1GC (默认) | -XX:+UseZGC | -XX:+UseShenandoahGC | -XX:+UseParallelGC

# 容器百分比
-XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=70 -XX:MinRAMPercentage=50
-XX:MaxDirectMemorySize=512m

# 统一日志
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags
-Xlog:safepoint=info

# 保险
-XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError

16. 三类“端到端”演练

A. 高并发接口 p99 抖动

  1. 收集:JFR.start 120s + -Xlog:gc*
  2. 分析:火焰图若显示 JSON 序列化分配风暴 → 启用复用缓冲/减少中间对象;
  3. 调参:G1 降 MaxGCPauseMillis→100,IHOP 从 45→30;
  4. 复测:看 p99 与 GC STW 总时长,确保不反向波动。

B. Netty OOMKill(容器)

  1. 发现:Pod 被 OOMKill,GC 正常;
  2. 定位:NMT summary + Netty 泄漏检测;
  3. 处理:-XX:MaxDirectMemorySize=1g,调低 Xmx,升级 Netty/修正 release;
  4. 预防:容器预留 R≥25%,Prometheus 监控直接内存与映射失败。

C. 大堆批处理速度慢

  1. 观测:CPU 低,GC 不频繁;
  2. 原因:单线程/锁粒度粗/IO 限速;
  3. 方案:切 Parallel GC,放大 ParallelGCThreads,并行化任务,批量 I/O;
  4. 验证:单位数据处理时间显著下降、Full GC 仍可接受。

17. 落地流程(团队级)

  1. 建立基线:统一 GC 模板、日志格式、JFR 取证模板;
  2. SLA 看板:p50/p95/p99、GC STW、分配速率、年轻/老年代占比;
  3. 容量演练:压测得到“资源-吞吐”曲线,标记经济高点;
  4. 问题库:沉淀“症状→证据→处方→工单链接”,上线前过 checklist。

18. 学习路径(从 0 到 1 到 10)

  1. 0→1:掌握 GC 分类、常用参数、会读 GC 日志 + JFR 火焰图;
  2. 1→5:能给不同场景选 GC 并解释取舍;容器化容量规划不踩坑;
  3. 5→10:结合业务特征做代码级降压、跨层诊断(DB/缓存/网络),把调优固化为模板与规范。

附:你可能会用到的“命令备忘”

# 统一日志(9+)
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime,level,tags
-Xlog:safepoint=info

# JFR(11+)
jcmd <pid> JFR.start name=profile settings=profile delay=10s duration=120s filename=/tmp/profile.jfr

# 堆/类/线程
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram
jcmd <pid> Thread.print

# 堆 dump
jmap -dump:live,file=/tmp/heap.hprof <pid>

# NMT
-XX:NativeMemoryTracking=summary
jcmd <pid> VM.native_memory summary

结语
JVM 调优不是堆参数清单,而是“业务目标→系统画像→证据驱动→小步验证”的工程化能力。把本手册的模板与 SOP 嵌入你的 CI/CD 与运维流程,你的 Java 服务会更“稳、快、省”。

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注

close
arrow_upward