JVM 调优不是堆砌参数的艺术,而是在吞吐量、延迟与内存占用之间找到业务场景的最优平衡点。本文从垃圾回收器原理出发,结合实战案例,建立系统化的调优方法论。
1. 调优三元论
吞吐量 (Throughput)
△
/|\
/ | \ 三者不可兼得
/ | \ 只能根据场景侧重优化
/ | \
/────┼────\
延迟 ←─────┼─────→ 内存占用
(Latency) |
| 场景 | 优先级 | 推荐回收器 |
|---|---|---|
| 批处理/大数据 | 吞吐量 > 延迟 | G1 / ParallelGC |
| Web 服务/API | 延迟 < 100ms | G1 / ZGC |
| 交易/支付系统 | 延迟 < 10ms | ZGC / Shenandoah |
| 嵌入式/容器 | 内存占用优先 | SerialGC / Epsilon |
2. 垃圾回收器演进
JDK 1.3 ── Serial + Serial Old
│
JDK 1.4 ── Parallel Scavenge + Parallel Old (吞吐量优先)
│
JDK 5 ── CMS (低延迟,已废弃)
│
JDK 7 ── G1 (分区回收)
│
JDK 11 ── ZGC (亚毫秒级,<10ms)
│
JDK 15 ── Shenandoah (红帽贡献,并发压缩)
│
JDK 21 ── Generational ZGC, EA Liberica Prime (Continuous Concurrent Marking)
3. G1 GC 深度解析
3.1 设计思想:分区回收 (Region-Based)
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ E │ E │ S │ S │ O │ O │ O │ H │
│ │ │ │ │ │ │ │ 1M │ ← Humongous (大对象)
│ 1M │ 1M │ 1M │ 1M │ 1M │ 1M │ 1M │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
Eden Eden Survivor Old Humongous
-XX:G1HeapRegionSize=1m (默认根据堆大小自动计算)
3.2 G1 回收过程
Young GC(暂停所有线程,STW)
→ 并行复制 Eden + Survivor → 新 Survivor 或 Old
Mixed GC(并发标记 + 筛选回收):
1. 初始标记 (Initial Mark) — STW,极短
2. 并发标记 (Concurrent Mark) — 与用户线程并发
3. 最终标记 (Remark) — STW
4. 筛选回收 (Cleanup) — STW,只回收价值最高的 Region
5. 并发清理 (Concurrent Cleanup) — 清理空 Region
Full GC(单线程 STW,避免):堆空间不足时触发,Serial Old 回退
3.3 G1 关键参数
# 目标最大暂停时间(默认 200ms)
-XX:MaxGCPauseMillis=200
# 每次 Mixed GC 回收老年代 Region 数上限
-XX:G1MixedGCCountTarget=8
# 触发全局并发标记的老年代占用阈值(默认 45%)
-XX:InitiatingHeapOccupancyPercent=45
# 回收停顿预测模型置信度
-XX:G1ConfidencePercent=50
# 避免大对象分配断裂
-XX:G1HeapWastePercent=5
4. ZGC:低延迟革命
4.1 设计目标
- 亚毫秒级 STW(通常 < 1ms)
- 堆大小无关性:10MB ~ 10TB 堆暂停时间相同
- 并发整理:回收过程与用户线程并行
4.2 核心技术:染色指针 (Colored Pointers)
64 位指针结构(Linux x86_64,使用 4 位元数据):
63 ────────────────── 47 ── 43 ── 42 ── 41 ── 40 ── 39 ──── 0
│ 未使用(17位) │ Final │ Remap │ Mark1 │ Mark0 │ 实际地址(42位) │
│ │ izable│ │ │ │ │
└─────────────────────┴───────┴───────┴───────┴───────┴──────────────┘
ZGC 元数据位 → 影响指针的染色状态
三色标记:
Marked0/Marked1:并发标记,交替使用Remapped:已重映射到新地址Finalizable:仅 Finalizer 对象可达
4.3 ZGC 回收周期
1. 暂停标记起始 (Pause Mark Start) STW ~0.05ms
└─ 根枚举(线程栈、寄存器、全局变量)
2. 并发标记 (Concurrent Mark) 并发
└─ 遍历对象图,染色指针标记可达
3. 暂停标记结束 (Pause Mark End) STW ~0.05ms
└─ 处理弱引用、类卸载
4. 并发预备重分配 (Concurrent Prepare) 并发
└─ 选择需要清理的 Region
5. 暂停重分配起始 (Pause Relocate Start) STW ~0.05ms
└─ 根枚举 + 设置转发表
6. 并发重分配 (Concurrent Relocate) 并发
└─ 移动存活对象,更新引用(读屏障拦截)
7. 并发重映射 (Concurrent Remap) 并发
└─ 清理旧地址引用,转移表回收
4.4 读屏障 (Load Barrier)
Object obj = field; // 字节码: getfield
// 伪代码:CPU 自动插入,无感知
if (obj & bad_mask) { // 指针染色位不正确
obj = slow_path(obj); // 修复指针,可能触发重映射
}
性能代价:读屏障成本约 4% CPU 开销,但换来极低延迟。
4.5 ZGC 参数
# JDK 21+:分代 ZGC(推荐)
-XX:+UseZGC
-XX:+ZGenerational # 分代模式(JDK 21 默认开启)
# 关键参数
-XX:ZCollectionInterval=5 # 强制 GC 间隔(seconds)
-XX:ZAllocationSpikeTolerance=2 # 分配速率容忍倍数
-XX:ZProactive=true # 主动触发 GC
# JDK 15-20:非分代 ZGC
-XX:+UseZGC
-XX:MaxGCPauseMillis=<target>
5. Shenandoah
与 ZGC 类似的目标和设计,JDK 12 GA(Red Hat 开发)。
独特点:
- 使用 Brooks Pointer(转发指针)而非染色指针
- 对象头中增加一个 forwarding 指针字段
- 读操作 O(1) 直接获取正确地址
适用场景:与 ZGC 几乎重叠,优先看 JDK 发行版支持(OpenJDK vs. Red Hat OpenJDK)。
6. GC 日志分析
6.1 JDK 9+ 统一日志格式
-Xlog:gc*:file=gc.log:time,uptime,pid,tid,level,tags:filecount=10,filesize=100m
6.2 日志解读:G1
[2023-01-15T10:23:45.123+0800][gc,start ] GC(42) Pause Young (Normal) (G1 Evacuation Pause)
[2023-01-15T10:23:45.124+0800][gc,task ] GC(42) Using 8 workers of 8 for evacuation
[...]
[2023-01-15T10:23:45.145+0800][gc,heap ] GC(42) Eden regions: 24->0(24)
[2023-01-15T10:23:45.145+0800][gc,heap ] GC(42) Survivor regions: 3->4(4)
[2023-01-15T10:23:45.145+0800][gc,heap ] GC(42) Old regions: 45->45
[2023-01-15T10:23:45.145+0800][gc,heap ] GC(42) Humongous regions: 1->1
[2023-01-15T10:23:45.145+0800][gc,metaspace] GC(42) Metaspace: 128M->128M(256M)
[2023-01-15T10:23:45.145+0800][gc ] GC(42) Pause Young (Normal) (G1 Evacuation Pause) 24M->8M(512M) 21.023ms
─────────────────────────────────────────────────────────
回收前 回收后 堆容量 暂停时间
6.3 分析工具
| 工具 | 用法 |
|---|---|
gcviewer | GUI 分析 GC 日志,生成吞吐/暂停图表 |
gceasy.io | 在线 GC 日志分析 |
jconsole | JMX 实时观察堆、GC、线程 |
jvisualvm | Visual GC 插件查看各区内存曲线 |
7. 常见内存问题排查
7.1 OOM 诊断矩阵
| 异常信息 | 原因 | 排查 |
|---|---|---|
Java heap space | 堆内存不足 | -Xmx 设置太小 / 内存泄漏 |
GC overhead limit exceeded | 98% 时间花在 GC,回收 < 2% | 检查 Full GC 频率 / 对象生命周期 |
Metaspace | 类元数据区溢出 | 动态生成类(CGLIB / Groovy) |
Direct buffer memory | 堆外内存泄漏 | NIO ByteBuffer / Netty 未释放 |
Unable to create new native thread | 线程数超限 | -Xss 太大 / ulimit -u 限制 |
7.2 内存泄漏排查步骤
1. -XX:+HeapDumpOnOutOfMemoryError
↓
2. jmap -dump:format=b,file=heap.hprof <pid>
↓
3. MAT (Memory Analyzer Tool) / JProfiler / VisualVM
↓
4. 查找 Dominator Tree 中 Retained Heap 最大的对象
↓
5. 追踪 GC Roots,定位泄露引用链
↓
6. 分析代码:缓存未设置 TTL / 监听未移除 / ThreadLocal 未清理
7.3 平响抖动案例:Young GC 频繁
现象:Young GC 每 5s 触发一次,每次 50ms,平响飙高。
分析:
# gc.log 频繁出现 [gc,start] Pause Young
# Eden 区域分配速率高 → 对象生命周期极短
方案:
- 增大 Eden(
-Xmn或-XX:G1NewSizePercent) - 优化代码:避免短生命周期大对象
- 对象池化(Netty ByteBuf、线程本地缓存)
7.4 平响飙升案例:Full GC
现象:每小时 Full GC,每次 3s。
分析:
# [gc ] GC(XX) Pause Full (System.gc())
# 或元空间 / 老年代晋升过多
方案:
- 排查
System.gc()调用(-XX:+DisableExplicitGC 或使用 -XX:+ExplicitGCInvokesConcurrent) - 增加堆大小,优化对象晋升阈值
- 检查大对象直接进入老年代(
-XX:PretenureSizeThreshold)
8. 容器环境下的 JVM
# JDK 8u191+ / JDK 9+ 原生支持容器内存限制
-XX:+UseContainerSupport # 默认开启(JDK 10+)
# 堆占容器内存的比例(默认 25%)
-XX:MaxRAMPercentage=75.0
-XX:InitialRAMPercentage=50.0
# JDK 8u191 之前的版本:
# 设置堆大小,不要超过容器 limit(否则被 OOM Killer)
-Xmx512m -Xms512m
# Kubernetes 资源请求示例
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi" # JVM 读取 cgroup limit
cpu: "2000m"
cgroup v1 vs v2 JDK 限制读取:JDK 15+ 完整支持 cgroup v2。容器平台的 JVM 版本不得低于此基线。
9. 调优清单
# 1. 选择目标
-XX:MaxGCPauseMillis=50
# 2. 选择回收器
-XX:+UseZGC # 延迟敏感
-XX:+UseG1GC # 均衡
-XX:+UseParallelGC # 吞吐量
# 3. 堆内存
-Xms4g -Xmx4g # 固定堆,避免动态调整开销
-Xmn1g # 年轻代
# 4. GC 日志
-Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=10,filesize=100m
# 5. OOM 诊断
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/
# 6. 调试(仅开发环境)
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintFlagsFinal # 输出所有 JVM 参数默认值
延伸阅读
- JVM 内存模型与类加载机制 — JVM 内存区域与字节码执行
- Java 高并发编程精要 — 并发场景下的内存与 GC 影响
- 监控诊断与可观测性 — Arthas 实时诊断工具
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。