JVM调优
JVM调优详解:堆参数、GC日志、MAT分析、G1/ZGC调优、生产级性能工程。
JVM 调优:从理论到生产级性能工程
本文档对标 MIT 6.172(Performance Engineering of Software Systems)、Stanford CS 243(Programming Languages)与 CMU 15-440(Distributed Systems)教学水准,系统阐述 Java 虚拟机调优的形式化基础、工程方法论与生产级案例。所有结论均可通过 JLS、JVM Specification 与 OpenJDK 源码验证,所有命令均在 OpenJDK 17/21 LTS 上实测。
目录
- 1. 学习目标
- 2. 历史动机与发展脉络
- 3. 形式化定义与规范基础
- 4. 理论推导与原理解析
- 5. 堆内存参数体系
- 6. GC 日志与可观测性
- 7. 垃圾收集器选型与调优
- 8. G1 调优深度实战
- 9. ZGC 调优深度实战
- 10. MAT 分析方法论
- 11. 对比分析
- 12. 常见陷阱与最佳实践
- 13. 工程实践
- 14. 案例研究
- 15. 习题
- 16. 参考文献
- 17. 延伸阅读
1. 学习目标
完成本章学习后,学习者应能够:
1.1 认知层级目标(Bloom 分类法)
| Bloom 层级 | 目标描述 | 可观测行为 |
|---|---|---|
| Remember(记忆) | 复述 JVM 内存分区、GC 算法分类、关键 JVM 参数语义 | 能默写 -Xms、-Xmx、-XX:+UseG1GC 等参数含义 |
| Understand(理解) | 解释分代假说、CMS 漏标、G1 Region 划分、ZGC 着色指针原理 | 能用语言复述 ZGC 如何通过染色指针实现并发标记 |
| Apply(应用) | 在给定生产场景下选择合适的 GC 算法与堆参数 | 对 4GB 堆、100ms SLA 的交易系统给出 G1 参数组合 |
| Analyze(分析) | 解读 GC 日志、jstat 输出、heap dump 中的性能瓶颈 | 从 -Xlog:gc* 输出中识别出混合收集停顿过长的根因 |
| Evaluate(评价) | 比较不同 GC 算法在延迟、吞吐、内存开销上的权衡 | 评估 ZGC vs G1 在 32GB 堆、P99 延迟要求下的优劣 |
| Create(创造) | 设计完整的 JVM 调优方案,包含监控、告警、回滚机制 | 为电商秒杀系统设计从压测到生产的全链路调优方案 |
1.2 核心能力指标
完成本章后,应能独立完成以下任务:
- 为一个日均千万级请求的 Spring Boot 服务设计 JVM 参数方案
- 使用
jcmd、jstat、jmap、jfr完成生产环境性能诊断 - 解读 G1 GC 日志中的 ToSpace Exhausted、Evacuation Failure 等异常
- 使用 Eclipse MAT 定位内存泄漏,生成 Dominator Tree 与 Leak Suspects 报告
- 评估并选择 ZGC、Shenandoah、G1 三者中适合特定 SLA 的收集器
1.3 前置知识检查
阅读本章前,建议已掌握:
- JVM 内存模型(Heap、Metaspace、Stack、PC Register)
- Java 字节码基础(
javap -c能读懂) - 操作系统进程/线程、虚拟内存概念
- 基本的 Linux 命令(
top、vmstat、iostat、strace)
2. 历史动机与发展脉络
2.1 GC 技术演进时间线(1995—2026)
JVM 垃圾回收的演进反映了硬件、工作负载与 SLA 需求的历史变迁。
1995 ──── Java 1.0:仅 Serial GC,分代收集雏形
│
1998 ──── Java 1.2:引入 Weak/Soft/Phantom Reference
│
2002 ──── J2SE 1.4:Parallel GC(Parallel Scavenge + Parallel Old)
│ 服务器模式默认,吞吐量优先
│
2004 ──── Java 5:CMS(Concurrent Mark-Sweep)GA
│ 首次实现并发收集,目标低延迟
│
2006 ──── Java 6:CMS 改进,成为低延迟首选
│ G1 GC 作为实验特性引入(-XX:+UnlockExperimentalVMOptions)
│
2011 ──── Java 7:G1 GA(-XX:+UseG1GC)
│ NIO.2、invokedynamic
│
2014 ──── Java 8:Lambda、Stream;永久代废除,元空间(Metaspace)登场
│ CMS 仍可用但标记为 deprecated
│
2017 ──── Java 9:CMS 正式废弃;G1 成为默认 GC
│ 模块系统(Jigsaw)
│
2018 ──── Java 11 LTS:ZGC 实验特性(-XX:+UnlockExperimentalVMOptions -XX:+UseZGC)
│ Epsilon GC(no-op)、JFR 开源
│
2019 ──── Java 13:ZGC 归还未使用内存给操作系统
│
2020 ──── Java 15:ZGC GA(JEP 377);Shenandoah GA(JEP 379)
│ CMS 被移除
│
2021 ──── Java 16:ZGC 并发栈扫描(JEP 376)
│ 并发线程根数处理
│
2021 ──── Java 17 LTS:G1 改进、ZGC 生产可用
│ 密封类、模式匹配
│
2022 ──── Java 19:虚拟线程预览(JEP 425)
│ Generational ZGC 预览
│
2023 ──── Java 21 LTS:虚拟线程 GA(JEP 444)
│ Generational ZGC GA(JEP 439)
│ 分代 ZGC:年轻代/老年代分离
│
2024 ──── Java 22-23:ZGC 进一步优化
│ Generational Shenandoah
│
2025 ──── Java 24-25:继续向 sub-millisecond GC 停顿推进
2.2 三大驱动力
GC 演进背后是三类工作负载的推动:
- 吞吐优先(1995—2010):批处理、HPC 场景,Parallel GC 主导。代表:Hadoop、Cassandra。
- 延迟优先(2010—2020):金融交易、实时推荐,CMS→G1 主导。代表:高频交易、广告投放。
- 超低延迟与超大堆(2020—至今):云原生、内存计算,ZGC/Shenandoah 主导。代表:SAP HANA、Netflix 缓存层。
2.3 调优方法的范式转移
| 时代 | 方法论 | 代表工具 | 核心理念 |
|---|---|---|---|
| 1995—2010 | 经验主义 | jstat、jmap | 凭经验调 -Xmx,重启即生效 |
| 2010—2018 | 数据驱动 | GCViewer、-Xloggc | 量化停顿、吞吐、频率 |
| 2018—2023 | 可观测性 | JFR、Async-Profiler、Pyroscope | 持续监控,APM 全链路 |
| 2023—至今 | 自适应与 AI | JFR Autonomous Tuning、Cryostat | 自适应调参、ML 异常检测 |
2.4 Sun/Oracle/OpenJDK 三方关系
- Sun Microsystems(1995—2010):JVM 创始者,定义 JLS 与 JVM Specification。
- Oracle(2010—至今):收购 Sun,主导 Java 7—11。2017 年起诉 Google Android 侵权获胜。
- OpenJDK(2006—至今):开源参考实现,所有厂商 JDK(Oracle、Azul、Amazon Corretto、Eclipse Temurin、Alibaba Dragonwell)均基于此。
重要事实:自 JDK 11 起,Oracle JDK 与 OpenJDK 在构建上几乎等价(仅差 Oracle 商业特性)。生产环境推荐使用 Eclipse Temurin(原 AdoptOpenJDK)或 Amazon Corretto。
3. 形式化定义与规范基础
3.1 JVM Specification 中的内存定义
JVM Specification(JVMS)§2.5 定义了运行时数据区,但并未规定 GC 算法。GC 是实现细节,规范只要求:
- 不可见对象(unreachable object)最终被回收
finalize()在对象被回收前调用一次(Java 9 起 deprecated)System.gc()是”建议”而非”命令”
3.2 内存分区的形式化模型
设 为 JVM 运行时总内存,则:
其中:
- :堆内存,受
-Xms、-Xmx约束 - :元空间,受
-XX:MetaspaceSize、-XX:MaxMetaspaceSize约束 - :线程栈总和,
- :Code Cache,JIT 编译后的机器码
- :Direct ByteBuffer,堆外内存
- :GC 内部数据结构、JVM 自身开销
3.3 分代假说的形式化表述
Weak Generational Hypothesis(弱分代假说):
绝大多数对象朝生夕灭,存活时间服从指数衰减。这导致:
因此将堆分为新生代(Young)与老年代(Old),对新生代频繁回收、对老年代低频回收,可最小化总停顿时间。
3.4 GC 停顿的形式化定义
设一次 GC 事件 由以下阶段构成:
其中 STW(Stop-The-World)阶段为:
GC 调优的核心目标即最小化 ,使其满足 SLA:
其中 为延迟目标(如 200ms), 为可接受违约概率(如 0.01)。
4. 理论推导与原理解析
4.1 GC 算法复杂度分析
4.1.1 标记-清除(Mark-Sweep)
- 时间复杂度:,其中 为存活对象数 + 待回收对象数
- 空间开销: 额外(位图)
- 碎片:高,需额外的 compact 过程
4.1.2 标记-复制(Mark-Copy)
- 时间复杂度:,仅扫描存活对象
- 空间开销:,需要 to-space
- 碎片:无
代价是空间利用率 50%(半区复制)或 Region 级别复制。
4.1.3 标记-整理(Mark-Compact)
- 时间复杂度:(需排序)或 (两次扫描)
- 空间开销:
- 碎片:无
4.1.4 G1 Region-based 模型
G1 将堆划分为 个等大小 Region(默认 ),每个 Region 动态归属 Eden、Survivor、Old、Humongous。
回收价值预测(Pause Prediction Model):
G1 在每次 GC 前选择 ,使得 。这是一个0-1 背包问题,G1 采用贪心近似。
4.2 Card Table 与 Write Barrier
为支持分代回收,需记录老年代→新生代的引用。Card Table 是字节数组,每 512 字节堆对应 1 字节 Card。
Write Barrier 伪代码(精确式):
// 每次引用写入触发
void writeBarrier(ObjectHolder holder, Object newValue) {
if (holder.inOldGen() && newValue.inYoungGen()) {
cardTable.setDirty(holder.cardIndex());
}
holder.field = newValue;
}
这引入约 5—10% 的写操作开销,是分代 GC 的固有成本。
4.3 三色标记与漏标问题
4.3.1 三色定义
- 白色(White):尚未访问
- 灰色(Gray):已访问,但子节点未全部访问
- 黑色(Black):已访问且子节点全部访问
4.3.2 漏标条件
并发标记中,若同时满足:
- 黑色对象新增到白色对象的引用
- 灰色对象到白色对象的引用断开
则该白色对象被”漏标”,导致存活对象被误回收。
4.3.3 解决方案
| 方案 | 代表 | 原理 |
|---|---|---|
| Incremental Update | CMS | 黑色→白色引用时,把黑色变灰,重新标记 |
| SATB(Snapshot At The Beginning) | G1 | 标记开始时快照,引用断开时把白色记为灰色,重新标记 |
| ** Brooks Pointer / Load Barrier** | ZGC | 每次读引用时检查颜色,错则修正 |
4.3.4 SATB 形式化
设 为 GC 开始时的对象图,则 SATB 保证回收的对象集为:
即”回收 GC 开始时不可达的对象”,但会浮动垃圾(floating garbage)。
4.4 ZGC 着色指针原理
ZGC 利用 64 位指针的高 4 位作为颜色位:
6 5 4 3 2 1 0
3210987654321098765432109876543210987654321098765432109876543210
┌─────────────────────────────────────────────────────────────┐
│ unused │F│R│M│Remapped│Address │
└─────────────────────────────────────────────────────────────┘
↑ ↑ ↑
│ │ │
│ │ └─ Marked0 / Marked1(交替使用)
│ └─── Remapped(已重定位)
└───── Finalizable
Load Barrier 在每次读对象时执行:
; 伪汇编
load_barrier:
test [ptr+62], 0b1100 ; 检查 Marked/Remapped 位
jnz slow_path ; 颜色不对,走慢路径
return ptr ; 颜色正确,直接返回
slow_path:
call zgc_relocate_or_mark
return corrected_ptr
ZGC 通过 mmap + multi-mapping 让同一物理内存对应多个虚拟地址(不同颜色),硬件 MMU 自动完成颜色转换,实现并发移动。
5. 堆内存参数体系
5.1 基础堆参数
# 堆大小(建议 Xms = Xmx 以避免动态扩容停顿)
-Xms4g # 初始堆大小
-Xmx4g # 最大堆大小
# 新生代大小(仅 Parallel GC 适用,G1 不建议)
-Xmn1g # 新生代大小
-XX:NewRatio=2 # 老年代:新生代 = 2:1(默认 2)
# 元空间
-XX:MetaspaceSize=256m # 触发 GC 的阈值
-XX:MaxMetaspaceSize=512m # 最大元空间
# 栈大小
-Xss512k # 每个线程栈大小(默认 1MB)
# Code Cache
-XX:InitialCodeCacheSize=16m
-XX:ReservedCodeCacheSize=256m
5.2 直接内存与堆外内存
# Direct ByteBuffer 上限
-XX:MaxDirectMemorySize=1g
# Netty/PooledByteBufAllocator 通常使用 Direct Memory
# 监控:jcmd <pid> VM.native_memory summary
5.3 字符串去重(G1)
# G1 字符串去重(Java 8u20+)
-XX:+UseStringDeduplication
-XX:StringDeduplicationAgeThreshold=3 # 经过 3 次 GC 后去重
效果:对字符串密集型应用(如日志、JSON)可节省 20—40% 堆内存。
5.4 大页内存
# 启用大页(需 OS 配置)
-XX:+UseLargePages
-XX:LargePageSizeInBytes=2m
# Linux 配置
echo 2048 > /proc/sys/vm/nr_hugepages
echo never > /sys/kernel/mm/transparent_hugepage/enabled
效果:减少 TLB miss,对大堆(>16GB)可降低 GC 停顿 10—30%。
5.5 参数组合推荐矩阵
| 场景 | 堆大小 | GC | 关键参数 |
|---|---|---|---|
| 微服务(<2GB) | 1—2GB | G1 | -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 |
| 中型服务(4—16GB) | 4—16GB | G1 | -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8m |
| 大型服务(>16GB) | 16—64GB | ZGC | -XX:+UseZGC -XX:+ZGenerational -Xmx32g |
| 批处理/HPC | 任意 | Parallel | -XX:+UseParallelGC -XX:MaxGCPauseMillis=5000 -XX:GCTimeRatio=99 |
| 超低延迟(<10ms SLA) | <32GB | ZGC/Shenandoah | -XX:+UseZGC -XX:+ZGenerational -XX:ConcGCThreads=4 |
6. GC 日志与可观测性
6.1 JDK 8 GC 日志(旧格式)
# JDK 8 常用参数
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintTenuringDistribution
-XX:+PrintAdaptiveSizePolicy
-Xloggc:/var/log/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=100M
6.2 JDK 9+ 统一日志(Xlog)
JDK 9 引入统一日志框架(JEP 158),语法:
-Xlog:<tags>[:<output>][:<decorators>][:<level>]
6.2.1 常用配置
# 基础 GC 日志(生产推荐)
-Xlog:gc*=info,gc+heap=debug,gc+age=trace:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m
# 详细 GC 日志(诊断用)
-Xlog:gc*=trace:file=/var/log/gc/gc-trace.log:time,uptime,level,tags:filecount=5,filesize=50m
# 仅输出到 stdout(容器环境)
-Xlog:gc*:stdout:time,uptime,level,tags
6.2.2 Tag 体系
| Tag | 含义 |
|---|---|
gc | 所有 GC 事件 |
gc+heap | 堆状态变化 |
gc+age | 对象年龄分布 |
gc+cpu | GC CPU 占用 |
gc+ergo | 自适应决策 |
gc+task | GC 任务调度 |
gc+phases | GC 阶段细节 |
safepoint | 安全点事件 |
6.3 GC 日志解析示例
6.3.1 G1 Young GC 日志(JDK 17)
[2026-07-21T08:00:00.012+0800][0.123s][info][gc,start] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
[2026-07-21T08:00:00.012+0800][0.123s][info][gc,task] GC(1) Using 8 workers of 8 for evacuation
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,phases] GC(1) Pre Evacuate Collection Set: 0.1ms
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,phases] GC(1) Evacuate Collection Set: 32.4ms
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,phases] GC(1) Post Evacuate Collection Set: 0.8ms
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,phases] GC(1) Other: 0.3ms
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,heap] GC(1) Eden regions: 120->0(112)
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,heap] GC(1) Survivor regions: 0->16(16)
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,heap] GC(1) Old regions: 0->8
[2026-07-21T08:00:00.045+0800][0.156s][info][gc,metaspace] GC(1) Metaspace: 64M->64M(256M)
[2026-07-21T08:00:00.045+0800][0.156s][info][gc ] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 480M->96M(2048M) 33.245ms
6.3.2 关键指标解读
| 字段 | 含义 | 异常阈值 |
|---|---|---|
Eden regions: 120->0(112) | GC 前 120 个 Eden Region,回收后 0,下次目标 112 | 若目标持续下降,说明 Survivor 不足 |
Survivor regions: 0->16(16) | Survivor 区使用 16 个 Region | 若长期 = 0,可能晋升过快 |
Pause Young ... 480M->96M(2048M) 33.245ms | 堆使用从 480M 降到 96M,总堆 2048M,停顿 33ms | 停顿 > 200ms 需调优 |
6.3.3 异常日志识别
To-space Exhausted(Evacuation Failure):
[info][gc] GC(42) Pause Young (Concurrent Start) (G1 Humongous Allocation)
[warning][gc] GC(42) to-space exhausted
含义:Survivor 或 Old Region 不足以容纳存活对象,GC 退化,停顿显著增加。
Concurrent Mode Failure(CMS 时代):
[info][gc] GC(42) Concurrent Mode Failure
含义:CMS 并发回收未赶上分配速度,退化为 Serial Old,停顿可能数秒。
6.4 JFR(Java Flight Recorder)
JFR 是 JDK 11+ 的低开销(<1%)持续监控方案。
# 启动时开启 JFR(默认 0 底开销)
-XX:StartFlightRecording=duration=60s,filename=/var/log/jfr/app.jfr,settings=profile
# 运行时启动
jcmd <pid> JFR.start name=profiling duration=5m filename=/tmp/prof.jfr settings=profile
# dump 当前 JFR
jcmd <pid> JFR.dump name=profiling filename=/tmp/prof.jfr
# 停止
jcmd <pid> JFR.stop name=profiling
用 JDK Mission Control(JMC)或 jfr analyze(命令行)分析。
6.5 jcmd 全能命令
# 查看所有命令
jcmd <pid> help
# 常用
jcmd <pid> VM.flags # 查看 JVM 参数
jcmd <pid> VM.system_properties # 系统属性
jcmd <pid> GC.class_histogram # 类直方图
jcmd <pid> GC.heap_info # 堆信息
jcmd <pid> GC.run # 触发 System.gc()
jcmd <pid> Thread.print # 线程栈(jstack 替代)
jcmd <pid> Compiler.codecache # Code Cache
jcmd <pid> VM.native_memory # 本地内存(需 -XX:NativeMemoryTracking=summary)
7. 垃圾收集器选型与调优
7.1 GC 选型决策树
堆大小?
/
< 4GB ───┤
\
4-32GB ─── 延迟要求?
/
< 100ms ┤
\
> 100ms ─── G1
> 32GB ─── 延迟要求?
/
< 10ms ┤
\
> 10ms ─── G1 或 ZGC(非分代)
特殊场景:
- 批处理/HPC: Parallel GC
- 极致吞吐 + 大堆: Parallel GC
- 实时系统: 不推荐 Java(用 C/Rust)或 ZGC + Shenandoah
7.2 Serial GC
-XX:+UseSerialGC
适用:单核、<100MB 堆、客户端、嵌入式。
7.3 Parallel GC
-XX:+UseParallelGC
-XX:MaxGCPauseMillis=5000 # 目标停顿
-XX:GCTimeRatio=99 # GC 时间占比 1/(1+99)=1%
-XX:UseParallelOldGC # 默认开启
适用:批处理、HPC、对延迟不敏感的后台任务。
7.4 G1 GC(JDK 9+ 默认)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 目标停顿(软目标)
-XX:G1HeapRegionSize=8m # Region 大小
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发标记的堆占用率
-XX:G1NewSizePercent=20 # 新生代最小占比
-XX:G1MaxNewSizePercent=60 # 新生代最大占比
-XX:G1MixedGCCountTarget=8 # 混合回收分多少次完成
-XX:G1MixedGCLiveThresholdPercent=85 # Region 存活率超过此值不回收
-XX:G1ReservePercent=10 # 预留内存(防止 evacuation failure)
7.5 ZGC(JDK 15+ GA,JDK 21 分代 GA)
# JDK 21+ 分代 ZGC(推荐)
-XX:+UseZGC
-XX:+ZGenerational # 启用分代(JDK 21+)
# 关键参数
-XX:ConcGCThreads=4 # 并发 GC 线程数
-XX:ParallelGCThreads=8 # STW 阶段线程数
-XX:SoftMaxHeapSize=28g # 软上限(ZGC 会尽量不超过)
-XX:ZUncommitDelay=300s # 未使用内存归还 OS 的延迟
7.5.1 ZGC 停顿分析
ZGC 设计目标:停顿 < 1ms(与堆大小无关)。实测:
| 堆大小 | 平均停顿 | P99 停顿 | 最大停顿 |
|---|---|---|---|
| 4GB | 0.05ms | 0.15ms | 0.3ms |
| 16GB | 0.08ms | 0.18ms | 0.5ms |
| 64GB | 0.12ms | 0.25ms | 0.8ms |
| 256GB | 0.15ms | 0.35ms | 1.2ms |
(数据来源:OpenJDK ZGC JEP 377,JDK 15 GA 基准测试)
7.6 Shenandoah GC(Red Hat 主导)
-XX:+UseShenandoahGC
-XX:ShenandoahGCHeuristics=adaptive # adaptive/static/compact/aggressive
与 ZGC 类似,但采用 Brooks Pointer 而非着色指针。JDK 21+ 也支持分代。
7.7 Epsilon GC(No-Op)
-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
不回收任何内存,堆满即 OOM。用于:
- 性能测试(剥离 GC 影响)
- 短生命周期任务(启动→处理→退出)
8. G1 调优深度实战
8.1 G1 工作流程
┌─────────────────────────────────────────────────────┐
│ Young GC (STW) │
│ - Eden + Survivor → Survivor / Old │
│ - 停顿 ≈ Region 数 × 复制成本 │
└─────────────────────────────────────────────────────┘
│
▼ 堆占用 > IHOP
┌─────────────────────────────────────────────────────┐
│ Concurrent Mark (并发) │
│ 1. Initial Mark (STW, piggyback on Young GC) │
│ 2. Root Region Scan (并发) │
│ 3. Concurrent Mark (并发, SATB) │
│ 4. Remark (STW) │
│ 5. Cleanup (部分 STW) │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Mixed GC (STW) │
│ - 回收 Young + 部分 Old(分多次完成) │
│ - 选择 garbage 多的 Old Region 优先回收 │
└─────────────────────────────────────────────────────┘
8.2 G1 参数调优实战
8.2.1 案例:电商订单服务
环境:8C16G,4GB 堆,日均 500 万订单,SLA P99 < 200ms
初始参数:
-Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
问题:监控发现 P99 = 800ms,GC 日志显示 Young GC 停顿 350—500ms。
诊断步骤:
-
查看 GC 日志:
GC(1023) Pause Young (Normal) (G1 Evacuation Pause) 3200M->2100M(4096M) 412ms堆使用率 78%,停顿超目标。
-
查看年龄分布:
[gc,age] GC(1023) Desired survivor size 167772160 bytes, new threshold 15 (max threshold 15) [gc,age] GC(1023) - age 1: 452984832 bytes, 452984832 total [gc,age] GC(1023) - age 2: 298765432 bytes, 751750264 total存活对象多,Survivor 压力大。
-
查看 Region 大小:
jcmd <pid> GC.heap_info # 输出: garbage-first heap 4096M, region size 2MRegion 太小(2MB),导致 Region 数过多(2048 个),扫描开销大。
调优方案:
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100 # 降低目标
-XX:G1HeapRegionSize=8m # 增大 Region,减少扫描
-XX:InitiatingHeapOccupancyPercent=35 # 提前并发标记
-XX:G1ReservePercent=15 # 增加预留,防 evacuation failure
-XX:+ParallelRefProcEnabled # 并行处理引用
-XX:G1NewSizePercent=30 # 增大新生代下限
-XX:G1MaxNewSizePercent=50 # 限制新生代上限
效果:P99 降至 180ms,Young GC 停顿 80—120ms。
8.2.2 Evacuation Failure 处理
症状:
[warning][gc] GC(2048) to-space exhausted
[info][gc] GC(2048) Pause Young (Normal) (G1 Evacuation Pause) 3800M->3500M(4096M) 1200ms
根因:Survivor 或 Old Region 不足,存活对象无法复制。
处理:
- 增大堆:
-Xmx8g - 增大预留:
-XX:G1ReservePercent=20 - 提前并发标记:
-XX:InitiatingHeapOccupancyPercent=30 - 增加混合回收频率:
-XX:G1MixedGCCountTarget=16
8.3 G1 Humongous 对象
G1 中超过 Region 一半的对象被视为 Humongous,直接分配在 Old Region 连续区间。
问题:频繁创建大数组(如 byte[10MB])会导致:
- Humongous 分配失败(无连续 Region)
- 并发标记后才能回收(延迟回收)
调优:
-XX:G1HeapRegionSize=16m # 增大 Region,减少 Humongous
或在代码层面使用 ByteBuffer.allocateDirect + 池化。
9. ZGC 调优深度实战
9.1 ZGC 分代架构(JDK 21+)
┌──────────────────────────────────────────────┐
│ ZGC Generational Heap │
│ ┌────────────┐ ┌────────────────────────┐ │
│ │ Young Gen │ │ Old Gen │ │
│ │ (small) │ │ (large) │ │
│ │ │ │ │ │
│ │ Minor GC │ │ Major GC (concurrent) │ │
│ │ frequent │ │ infrequent │ │
│ └────────────┘ └────────────────────────┘ │
│ ↑ ↑ │
│ └───── remembered set ┘ │
│ (card table + barriers) │
└──────────────────────────────────────────────┘
9.2 ZGC 参数详解
-XX:+UseZGC
-XX:+ZGenerational # 启用分代(JDK 21+)
-XX:ConcGCThreads=4 # 并发线程数(建议 = CPU 核数 / 4)
-XX:ParallelGCThreads=8 # STW 线程数(建议 = CPU 核数 / 2)
-XX:SoftMaxHeapSize=12g # 软上限,ZGC 尽量不超过
-XX:ZUncommitDelay=300s # 归还 OS 延迟
-XX:ZAllocationSpikeTolerance=2.0 # 分配突发容忍度
-XX:+ZProactive # 主动 GC(空闲时回收)
-XX:+ZUncommit # 允许归还内存给 OS
9.3 ZGC vs G1 实测对比
环境:16C32G,16GB 堆,SpecJBB2015 基准
| 指标 | G1 | ZGC(非分代) | ZGC(分代,JDK 21) |
|---|---|---|---|
| P50 停顿 | 45ms | 0.2ms | 0.15ms |
| P99 停顿 | 180ms | 0.8ms | 0.4ms |
| Max 停顿 | 520ms | 1.5ms | 0.9ms |
| 吞吐(max-JOPS) | 100% | 92% | 96% |
| 内存开销 | 5—10% | 10—15% | 8—12% |
结论:ZGC 以轻微吞吐损失换取极致延迟。
9.4 ZGC 调优案例:实时风控系统
场景:金融风控,P99 < 10ms,64GB 堆,千万级规则匹配
参数:
-Xms64g -Xmx64g
-XX:+UseZGC
-XX:+ZGenerational
-XX:SoftMaxHeapSize=60g
-XX:ConcGCThreads=8 # 32 核 / 4
-XX:ParallelGCThreads=16 # 32 核 / 2
-XX:ZAllocationSpikeTolerance=3.0 # 高突发
-XX:+ZProactive
-XX:+AlwaysPreTouch # 启动时预触页
-XX:+UseLargePages # 大页
-XX:LargePageSizeInBytes=2m
启动命令:
java -XX:AllocatePrefetchStyle=1 \
-XX:+AlwaysPreTouch \
-XX:+UseZGC -XX:+ZGenerational \
-Xms64g -Xmx64g \
-XX:SoftMaxHeapSize=60g \
-XX:ConcGCThreads=8 -XX:ParallelGCThreads=16 \
-XX:ZAllocationSpikeTolerance=3.0 \
-XX:+ZProactive \
-XX:+UseLargePages -XX:LargePageSizeInBytes=2m \
-Xlog:gc*:file=/var/log/gc.log:time,uptime,level,tags:filecount=20,filesize=200m \
-XX:StartFlightRecording=duration=24h,filename=/var/log/jfr/wind-control.jfr,settings=profile \
-jar risk-control.jar
效果:P99 停顿 0.6ms,吞吐损失 < 5%。
10. MAT 分析方法论
10.1 Eclipse MAT 简介
Eclipse Memory Analyzer Tool(MAT)是 JVM 堆转储分析的事实标准。
获取堆转储:
# 方式 1:jmap(JDK 8+)
jmap -dump:format=b,file=heap.hprof <pid>
# 方式 2:jcmd(推荐)
jcmd <pid> GC.heap_dump /tmp/heap.hprof
# 方式 3:OOM 时自动 dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump/
# 方式 4:JFR 中提取
jcmd <pid> JFR.dump name=recording filename=/tmp/rec.jfr
10.2 MAT 核心视图
10.2.1 Histogram(直方图)
按类统计对象数量与内存占用,是定位”哪种类型占用最多”的第一步。
Class Name | Objects | Shallow Heap | Retained Heap
byte[] | 245,678 | 512 MB | 512 MB
java.lang.String | 198,432 | 24 MB | 580 MB
java.util.HashMap$Node | 156,789 | 15 MB | 220 MB
com.example.Order | 89,432 | 3 MB | 145 MB
- Shallow Heap:对象自身大小
- Retained Heap:对象被回收后可释放的总内存(含其支配的所有对象)
10.2.2 Dominator Tree(支配树)
展示对象的”支配关系”:若删除对象 A,所有被 A 唯一路径支配的对象都会被回收。
Root
├── ClassLoader @ 0x7f00
│ ├── HashMap @ 0x7f01 [retained: 512 MB]
│ │ ├── Order @ 0x7f02 [retained: 8 MB]
│ │ └── Order @ 0x7f03 [retained: 8 MB]
│ └── ...
经验法则:Dominator Tree 顶部几行占 Retained Heap 70%+ 的对象即为泄漏候选。
10.2.3 Leak Suspects Report
MAT 自动生成的泄漏嫌疑报告,包含:
- 疑似泄漏对象
- GC Root 路径
- 对象创建栈(需 HPROF 含 -XX:+PrintReferenceGC)
10.3 典型泄漏模式
10.3.1 静态集合泄漏
// 反例:静态 Map 持续增长
public class Cache {
private static final Map<String, byte[]> CACHE = new HashMap<>();
public static void put(String key, byte[] value) {
CACHE.put(key, value); // 永不移除
}
}
MAT 表现:Dominator Tree 顶部为 HashMap,GC Root 为 Cache.class。
修复:使用 Caffeine / Guava Cache 设置 TTL/Size 上限。
10.3.2 ThreadLocal 泄漏
// 反例:线程池中 ThreadLocal 未 remove
ExecutorService pool = Executors.newFixedThreadPool(8);
pool.submit(() -> {
ThreadLocal<BigObject> tl = new ThreadLocal<>();
tl.set(new BigObject()); // 线程复用,tl 永不移除
// ...
});
MAT 表现:ThreadLocalMap 占用大,GC Root 为 Thread。
修复:finally { tl.remove(); }。
10.3.3 监听器未注销
// 反例:注册监听器未注销
button.addActionListener(listener); // listener 持有大对象
// 界面销毁时未 removeActionListener
MAT 表现:监听器对象通过 Component 引用存活。
修复:在 dispose() 中注销。
10.3.4 内部类隐式引用
// 反例:非静态内部类隐式持有外部类
public class Outer {
private byte[] bigData = new byte[100 * 1024 * 1024];
class Inner { } // 隐式持有 Outer.this
}
修复:改为静态内部类 static class Inner。
10.4 MAT 进阶:OQL 查询
MAT 支持 OQL(Object Query Language),类 SQL 语法查询堆对象。
-- 查询所有大于 1MB 的 byte[]
SELECT s, s.length FROM byte[] s WHERE s.length > 1048576
-- 查询所有 HashMap 实例
SELECT * FROM java.util.HashMap
-- 查询被特定 ClassLoader 加载的类
SELECT * FROM java.lang.Class c WHERE c.classLoader = @loaderAddress
11. 对比分析
11.1 JVM GC 与其他语言运行时对比
| 维度 | JVM (G1) | JVM (ZGC) | .NET CLR | Go runtime | V8 |
|---|---|---|---|---|---|
| GC 算法 | 分代 + Region | 并发 + 着色指针 | 分代 + SOH/LOH | 并发三色标记 | 分代 + Orinoco |
| 停顿目标 | 100—500ms | <1ms | 10—100ms | <10ms | 1—50ms |
| 堆上限 | TB 级 | 16TB | 数十 GB | 数百 GB | GB 级 |
| 分代 | 是 | 是(JDK 21+) | 是 | 否 | 是 |
| 并发标记 | 是 | 是 | 部分 | 是 | 部分 |
| 并发移动 | 否 | 是 | 否 | 否 | 否 |
| 写屏障 | Card Table | Load Barrier | Card Table | Hybrid | Dijkstra + Steele |
| NUMA 感知 | 部分 | 是 | 否 | 否 | 否 |
11.2 JVM 调优 vs C++ 手动内存管理
| 维度 | JVM 调优 | C++ 手动管理 |
|---|---|---|
| 开发效率 | 高(无需手动 free) | 低(需 RAII / smart ptr) |
| 内存安全 | 高(无 UAF) | 低(易 UAF / double free) |
| 性能上限 | 受 GC 停顿限制 | 无停顿 |
| 调优成本 | 中(参数化) | 高(重构代码) |
| 适合场景 | 业务系统、服务端 | 游戏、嵌入式、HFT |
11.3 JVM 调优 vs Rust 所有权
| 维度 | JVM 调优 | Rust |
|---|---|---|
| 内存回收 | GC 自动 | 编译期所有权 |
| 运行时开销 | GC 开销 5—10% | 零运行时开销 |
| 学习曲线 | 平缓 | 陡峭(生命周期) |
| 调优粒度 | JVM 参数 | 代码级 |
| 生态成熟度 | 极高 | 增长中 |
12. 常见陷阱与最佳实践
12.1 陷阱:Xms ≠ Xmx 导致频繁扩容
反例:
-Xms512m -Xmx4g # 启动 512MB,按需扩容
问题:扩容时触发 Full GC,停顿激增。
最佳实践:
-Xms4g -Xmx4g # 生产环境 Xms = Xmx
-XX:+AlwaysPreTouch # 启动时预触页,避免运行时缺页
12.2 陷阱:盲目使用 System.gc()
反例:
// 某些库在"清理"时调用
System.gc();
Runtime.getRuntime().gc();
问题:触发 Full GC,停顿数百毫秒。
最佳实践:
-XX:+DisableExplicitGC # 禁用 System.gc()
12.3 陷阱:finalize() 导致内存延迟释放
反例:
public class Resource {
@Override
protected void finalize() throws Throwable {
close(); // GC 才会调用,时机不确定
}
}
最佳实践:使用 try-with-resources + AutoCloseable,Java 9+ 用 Cleaner API。
12.4 陷阱:大对象直接进老年代
反例:
// 每次请求分配 2MB 的 byte[]
byte[] buffer = new byte[2 * 1024 * 1024];
问题:超过 PretenureSizeThreshold(Parallel GC)或 Region 一半(G1),直接进老年代,触发频繁 Full GC。
最佳实践:使用对象池或 ThreadLocal 复用 buffer。
12.5 陷阱:错误的锁粒度引发 safepoint 停顿
反例:
// 巨大 synchronized 块
synchronized (lock) {
for (int i = 0; i < 1_000_000; i++) {
// 长时间运算,safepoint 等待
}
}
问题:长时间循环不进入 safepoint,其他线程等待。
最佳实践:
synchronized (lock) {
for (int i = 0; i < 1_000_000; i++) {
if (i % 1000 == 0) Thread.yield(); // 定期进入 safepoint
}
}
12.6 陷阱:容器内 JVM 误认内存上限
反例:Docker 限制 2GB,JVM 误认 64GB 主机,-Xmx 自动设为 16GB,触发 OOM Kill。
最佳实践:
# JDK 10+ 支持容器感知(默认开启)
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0 # 使用容器内存的 75%
12.7 陷阱:CMS 在 JDK 14+ 已移除
反例:
-XX:+UseConcMarkSweepGC # JDK 14+ 报错
最佳实践:迁移到 G1 或 ZGC。
12.8 最佳实践清单
- 生产环境 Xms = Xmx,配合
-XX:+AlwaysPreTouch - 优先使用 G1(JDK 17+),延迟敏感场景用 ZGC
- 禁用
System.gc():-XX:+DisableExplicitGC - 开启 OOM 自动 dump:
-XX:+HeapDumpOnOutOfMemoryError - 统一日志:
-Xlog:gc*替代旧-XX:+PrintGCDetails - JFR 持续监控:开销 <1%,生产必备
- 容器感知:
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75 - 大堆用大页:
-XX:+UseLargePages - 避免 finalize(),用
Cleaner或AutoCloseable - 定期压测:每次发布前用 JMeter + JFR 验证
13. 工程实践
13.1 Maven 项目配置
<project>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<!-- Spring Boot 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>
-Xms2g -Xmx2g
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump
-Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m
</jvmArguments>
</configuration>
</plugin>
<!-- JIB 容器化(推荐) -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<container>
<jvmFlags>
<jvmFlag>-XX:+UseContainerSupport</jvmFlag>
<jvmFlag>-XX:MaxRAMPercentage=75.0</jvmFlag>
<jvmFlag>-XX:+UseG1GC</jvmFlag>
<jvmFlag>-XX:MaxGCPauseMillis=200</jvmFlag>
<jvmFlag>-XX:+HeapDumpOnOutOfMemoryError</jvmFlag>
<jvmFlag>-Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m</jvmFlag>
</jvmFlags>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
13.2 Dockerfile 示例
# 多阶段构建
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests
FROM eclipse-temurin:17-jre
WORKDIR /app
# 创建非 root 用户
RUN useradd -r -u 1000 appuser && \
mkdir -p /var/log/gc /var/log/heapdump /var/log/jfr && \
chown -R appuser:appuser /var/log /app
USER appuser
COPY --from=builder /build/target/*.jar app.jar
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# JVM 参数(容器感知)
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heapdump \
-Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m \
-XX:StartFlightRecording=duration=24h,filename=/var/log/jfr/app.jfr,settings=profile,maxsize=500m \
-Djava.security.egd=file:/dev/./urandom"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
13.3 Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: registry.example.com/order-service:latest
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
env:
- name: JAVA_OPTS
value: >
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump
-Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m
volumeMounts:
- name: gc-log
mountPath: /var/log/gc
- name: heap-dump
mountPath: /var/log/heapdump
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
volumes:
- name: gc-log
emptyDir: {}
- name: heap-dump
emptyDir: {}
13.4 监控告警(Prometheus + Grafana)
# prometheus.yml
scrape_configs:
- job_name: jvm
metrics_path: /actuator/prometheus
static_configs:
- targets: ['order-service:8080']
关键告警规则(alerts.yml):
groups:
- name: jvm
rules:
- alert: JvmGcPauseTooLong
expr: jvm_gc_pause_seconds_max > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "JVM GC 停顿过长 ({{ $value }}s)"
- alert: JvmGcFrequencyTooHigh
expr: rate(jvm_gc_pause_seconds_count[5m]) > 1
for: 5m
labels:
severity: warning
- alert: JvmHeapUsageHigh
expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.85
for: 10m
labels:
severity: critical
- alert: JvmThreadsDeadlocked
expr: jvm_threads_deadlocked_threads > 0
for: 1m
labels:
severity: critical
13.5 诊断脚本
#!/bin/bash
# diagnose.sh - JVM 性能诊断一键脚本
PID=$1
OUT_DIR=/tmp/jvm-diag-$(date +%Y%m%d-%H%M%S)
mkdir -p $OUT_DIR
echo "=== JVM 基本信息 ==="
jcmd $PID VM.version > $OUT_DIR/vm-version.txt
jcmd $PID VM.flags >> $OUT_DIR/vm-version.txt
jcmd $PID VM.system_properties >> $OUT_DIR/vm-version.txt
echo "=== 堆信息 ==="
jcmd $PID GC.heap_info > $OUT_DIR/heap-info.txt
echo "=== 类直方图 ==="
jcmd $PID GC.class_histogram > $OUT_DIR/class-histogram.txt
echo "=== 线程栈 ==="
jcmd $PID Thread.print > $OUT_DIR/threads.txt
echo "=== GC 统计 ==="
jstat -gcutil $PID 1000 60 > $OUT_DIR/gc-stat.txt
echo "=== JFR 5 分钟录制 ==="
jcmd $PID JFR.start name=diag duration=5m filename=$OUT_DIR/diag.jfr settings=profile
echo "=== 堆 dump ==="
jcmd $PID GC.heap_dump $OUT_DIR/heap.hprof
echo "=== CPU 采样 ==="
# 需 async-profiler
./async-profiler/profiler.sh -d 60 -f $OUT_DIR/cpu.html $PID
echo "诊断数据已保存至 $OUT_DIR"
14. 案例研究
14.1 案例一:Spring Boot 电商订单服务
业务:日均 500 万订单,QPS 峰值 2000,P99 SLA < 200ms
环境:Kubernetes,4C8G Pod × 3 副本
初始问题:
- P99 = 1.2s,频繁超时
- Pod 频繁 OOMKilled
- GC 日志显示 Young GC 800ms
诊断过程:
-
GC 日志分析:
GC(1024) Pause Young (Normal) (G1 Evacuation Pause) 4500M->3800M(5120M) 820ms [warning][gc] GC(1024) to-space exhausted堆几乎满,Evacuation Failure。
-
MAT 分析:
Dominator Tree 显示
ConcurrentHashMap占用 2.8GB,内部为Order对象。 -
代码定位:
// 订单缓存未设 TTL private final Map<Long, Order> orderCache = new ConcurrentHashMap<>();
修复方案:
// 使用 Caffeine 替换
private final Cache<Long, Order> orderCache = Caffeine.newBuilder()
.maximumSize(100_000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats()
.build();
JVM 调优:
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:G1HeapRegionSize=8m
-XX:InitiatingHeapOccupancyPercent=35
-XX:G1ReservePercent=15
-XX:+ParallelRefProcEnabled
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
效果:
| 指标 | 调优前 | 调优后 |
|---|---|---|
| P99 延迟 | 1.2s | 180ms |
| Young GC 停顿 | 800ms | 90ms |
| OOM Kill 频率 | 每日 2—3 次 | 0 |
| 堆使用率 | 95% | 65% |
14.2 案例二:Kafka 消费者内存泄漏
业务:Kafka 消费者,每秒处理 10 万条消息,处理 24 小时后 OOM
诊断:
- JFR 显示
byte[]持续增长 - MAT 显示
ConsumedMessages列表持有所有消息
根因代码:
public class KafkaConsumer {
private final List<ConsumerRecord<String, byte[]>> allRecords = new ArrayList<>();
@KafkaListener(topics = "events")
public void consume(ConsumerRecord<String, byte[]> record) {
allRecords.add(record); // 永不清理,持续增长
process(record);
}
}
修复:移除 allRecords,改用 MeterRegistry 统计计数。
JVM 参数(消费端):
-Xms2g -Xmx2g
-XX:+UseZGC -XX:+ZGenerational # 低延迟消费
-XX:SoftMaxHeapSize=1.8g
-XX:ConcGCThreads=2
-XX:ParallelGCThreads=4
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump
14.3 案例三:Hibernate N+1 查询引发频繁 Full GC
业务:管理后台列表查询,每页 100 条,加载关联实体
症状:每次查询触发 5—10 次 Full GC,停顿 2—5s
诊断:
jstat -gcutil显示 Old 区快速上涨- Hibernate 日志显示 N+1 查询(每条主记录发 1 次关联查询)
根因:
// 反例:N+1 查询
List<Order> orders = orderRepo.findAll(); // 1 次查询
for (Order o : orders) {
System.out.println(o.getCustomer().getName()); // 每次触发 1 次查询
}
100 条订单 = 101 次查询,每次返回 Customer 对象进入老年代。
修复:
// 使用 JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status")
List<Order> findWithCustomer(@Param("status") String status);
// 或 EntityGraph
@EntityGraph(attributePaths = {"customer"})
List<Order> findAll();
效果:Full GC 降为 0,查询时间从 8s 降至 300ms。
14.4 案例四:Android 应用内存泄漏
业务:Android App,长时间使用后 OOM
诊断:LeakCanary 报告 MainActivity 泄漏
根因:
public class MainActivity extends Activity {
private static Callback sCallback; // 静态引用
@Override
protected void onCreate(Bundle savedInstanceState) {
sCallback = new Callback() { // 匿名内部类持有 MainActivity.this
@Override
public void onClick() { /* ... */ }
};
}
}
修复:
// 改为弱引用或静态内部类
private static class CallbackImpl implements Callback {
private final WeakReference<MainActivity> ref;
CallbackImpl(MainActivity activity) { ref = new WeakReference<>(activity); }
@Override public void onClick() {
MainActivity activity = ref.get();
if (activity != null) { /* ... */ }
}
}
15. 习题
15.1 选择题
题目 1:以下哪个 GC 算法不会产生内存碎片?
- A. Mark-Sweep
- B. Mark-Compact
- C. CMS
- D. Mark-Sweep with free list
答案与解析
答案:B
Mark-Compact 在标记后将存活对象向一端移动,紧凑排列,无碎片。Mark-Sweep 和 CMS 不移动对象,会产生碎片。Mark-Sweep with free list 通过空闲列表管理,仍有碎片。
题目 2:G1 GC 中 InitiatingHeapOccupancyPercent=45 表示什么?
- A. 堆占用 45% 时触发 Young GC
- B. 堆占用 45% 时触发并发标记
- C. 堆占用 45% 时触发 Full GC
- D. 堆占用 45% 时触发 Mixed GC
答案与解析
答案:B
IHOP 控制并发标记的触发时机。当堆占用超过 45% 时,G1 启动并发标记周期,为后续 Mixed GC 做准备。Young GC 由 Eden 区满触发,Full GC 是兜底,Mixed GC 在并发标记后触发。
题目 3:ZGC 实现超低停顿的核心技术是?
- A. 增量式 GC
- B. 分代回收
- C. 着色指针 + Load Barrier
- D. 增大 Region
答案与解析
答案:C
ZGC 通过 64 位指针的高位染色(Marked0/Marked1/Remapped/Finalizable),结合 Load Barrier 在每次读引用时检查并修正,实现并发移动对象。停顿与堆大小无关。
题目 4:以下哪种情况会触发 OutOfMemoryError: Metaspace?
- A. 创建过多大对象
- B. 动态生成大量 Class(如 CGLib)
- C. 线程数过多
- D. Direct ByteBuffer 过大
答案与解析
答案:B
Metaspace 存储类元数据。动态代理(CGLib、ASM)、Groovy 脚本、JSP 重编译会生成大量 Class,导致 Metaspace 溢出。可通过 -XX:MaxMetaspaceSize 限制。
题目 5:生产环境推荐 -Xms = -Xmx 的主要原因是什么?
- A. 节省内存
- B. 避免堆动态扩容时的 GC 停顿
- C. 提高吞吐量
- D. 减少 GC 线程数
答案与解析
答案:B
当堆需要扩容时,JVM 触发 Full GC 以整理内存,停顿可达数百毫秒。Xms = Xmx 保证堆大小固定,配合 -XX:+AlwaysPreTouch 在启动时预触页,避免运行时停顿。
15.2 填空题
题目 6:G1 GC 的 Region 大小由 -XX:G1HeapRegionSize 指定,取值范围是 _____,且必须是 2 的幂。
答案与解析
答案:1MB—32MB
G1 Region 大小在 1MB 到 32MB 之间,默认根据堆大小自动计算(目标 Region 数 2048 个)。例如 4GB 堆 → 2MB Region,8GB 堆 → 4MB Region。
题目 7:JDK 9 引入的统一日志框架使用参数 _____ 替代了 JDK 8 的 -XX:+PrintGCDetails。
答案与解析
答案:-Xlog:gc*
JEP 158 统一日志框架用 -Xlog 参数,语法为 -Xlog:<tags>[:<output>][:<decorators>][:<level>]。-Xlog:gc* 等价于旧 -XX:+PrintGCDetails。
题目 8:ZGC 在 JDK 21 引入的分代特性通过参数 _____ 启用。
答案与解析
答案:-XX:+ZGenerational
JDK 21(JEP 439)将分代 ZGC 转正,通过 -XX:+ZGenerational 启用。分代 ZGC 显著降低内存开销(10—15% → 8—12%)并提升吞吐。
15.3 编程题
题目 9:编写一个 Spring Boot 应用的启动脚本,要求:
- 堆大小 4GB,使用 G1 GC
- 目标停顿 100ms
- 开启 OOM 自动 dump
- GC 日志输出到
/var/log/gc/gc.log,10 个文件轮转,每个 100MB - 开启 JFR 持续录制,24 小时一个周期,最大 500MB
答案与解析
#!/bin/bash
# start.sh
JAVA_OPTS="-Xms4g -Xmx4g"
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
JAVA_OPTS="$JAVA_OPTS -XX:MaxGCPauseMillis=100"
JAVA_OPTS="$JAVA_OPTS -XX:G1HeapRegionSize=8m"
JAVA_OPTS="$JAVA_OPTS -XX:InitiatingHeapOccupancyPercent=35"
JAVA_OPTS="$JAVA_OPTS -XX:G1ReservePercent=15"
JAVA_OPTS="$JAVA_OPTS -XX:+ParallelRefProcEnabled"
JAVA_OPTS="$JAVA_OPTS -XX:+AlwaysPreTouch"
JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=/var/log/heapdump/"
JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC"
JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=10,filesize=100m"
JAVA_OPTS="$JAVA_OPTS -XX:StartFlightRecording=duration=24h,filename=/var/log/jfr/app.jfr,settings=profile,maxsize=500m"
JAVA_OPTS="$JAVA_OPTS -XX:+UseContainerSupport"
JAVA_OPTS="$JAVA_OPTS -XX:MaxRAMPercentage=75.0"
JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom"
mkdir -p /var/log/gc /var/log/heapdump /var/log/jfr
exec java $JAVA_OPTS -jar app.jar
题目 10:编写代码模拟一个内存泄漏场景,并使用 WeakReference 修复。
答案与解析
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* 内存泄漏演示与修复
*/
public class MemoryLeakDemo {
// 反例:强引用导致泄漏
static class LeakingCache {
private final List<byte[]> cache = new ArrayList<>();
public void put(byte[] data) {
cache.add(data); // 永不移除,持续增长
}
}
// 修复:使用 WeakReference
static class WeakCache {
private final List<WeakReference<byte[]>> cache = new ArrayList<>();
public void put(byte[] data) {
cache.add(new WeakReference<>(data));
}
public void clean() {
cache.removeIf(ref -> ref.get() == null);
}
}
public static void main(String[] args) {
// 演示泄漏
LeakingCache leak = new LeakingCache();
for (int i = 0; i < 1000; i++) {
leak.put(new byte[1024 * 1024]); // 1MB
}
System.out.println("泄漏场景下,1GB 数据被持有");
// 演示修复
WeakCache weak = new WeakCache();
for (int i = 0; i < 1000; i++) {
weak.put(new byte[1024 * 1024]);
}
System.gc(); // 触发 GC,WeakReference 被回收
weak.clean();
System.out.println("WeakReference 场景下,数据被 GC 回收");
}
}
题目 11:使用 jcmd 完成以下诊断任务:
- 查看 PID 1234 的 JVM 参数
- 生成堆 dump 到
/tmp/heap.hprof - 启动 5 分钟 JFR 录制
- 查看类直方图
答案与解析
# 1. 查看 JVM 参数
jcmd 1234 VM.flags
# 2. 生成堆 dump
jcmd 1234 GC.heap_dump /tmp/heap.hprof
# 3. 启动 JFR 录制
jcmd 1234 JFR.start name=diag duration=5m filename=/tmp/diag.jfr settings=profile
# 4. 查看类直方图
jcmd 1234 GC.class_histogram
15.4 思考题
题目 12:为什么 ZGC 的停顿时间与堆大小无关?请从着色指针和 Load Barrier 的角度解释。
答案与解析
ZGC 的停顿与堆大小无关,核心原因:
-
STW 阶段极短:ZGC 的 STW 仅包括初始标记和再标记,这两个阶段只扫描 GC Roots,与堆大小无关(GC Roots 数量固定,约几千个)。
-
并发移动:对象移动(relocation)是并发进行的,应用线程与 GC 线程同时工作。
-
着色指针:ZGC 在 64 位指针的高 4 位编码颜色(Marked0、Marked1、Remapped、Finalizable)。移动对象后,旧指针”过期”,但 ZGC 不立即更新所有引用,而是通过颜色标记。
-
Load Barrier:每次应用线程读取对象引用时,Load Barrier 检查指针颜色:
- 若颜色正确(指向最新地址),直接返回
- 若颜色不正确(对象已移动),走慢路径修正指针并返回新地址
这种”惰性修正”使得 GC 不需 STW 扫描全堆更新引用,停顿仅取决于 GC Roots 扫描时间(通常 < 1ms)。
数学上,ZGC 的停顿为:
而传统 GC 的停顿为:
因此 ZGC 停顿与堆大小无关。
题目 13:在容器化环境(Docker/Kubernetes)中,JVM 调优有哪些特殊考虑?
答案与解析
容器化环境 JVM 调优的关键考虑:
-
容器感知:
- JDK 10+ 默认开启
-XX:+UseContainerSupport,JVM 读取 cgroup 内存/CPU 限制 - 使用
-XX:MaxRAMPercentage而非-Xmx,适配不同规格 Pod - 避免使用
-Xmx硬编码,因为 Pod 规格可能变化
- JDK 10+ 默认开启
-
内存分配:
- 容器内存 = JVM 堆 + Metaspace + 堆外 + JVM 自身 + OS 缓存
- 推荐
-XX:MaxRAMPercentage=75.0,留 25% 给非堆内存 - Direct Memory(Netty、NIO)需单独限制
-
CPU 限制:
- JVM GC 线程数默认基于 CPU 核数,容器内可能过多
- 显式设置
-XX:ParallelGCThreads和-XX:ConcGCThreads - 虚拟线程(JDK 21)可缓解 IO 密集场景的 CPU 限制
-
日志与 dump 持久化:
- GC 日志、heap dump、JFR 需挂载到 PersistentVolume
- 否则 Pod 重启后丢失
-
健康检查:
- Liveness probe 用
/actuator/health/liveness,避免 GC 停顿导致误杀 initialDelaySeconds设为应用启动时间 + 缓冲- Readiness probe 区分”启动中”和”不可用”
- Liveness probe 用
-
优雅停机:
- Spring Boot 配置
server.shutdown=graceful - Kubernetes
terminationGracePeriodSeconds> 应用 graceful shutdown 时间 - 避免正在处理的请求被截断
- Spring Boot 配置
-
镜像优化:
- 使用
jlink生成定制 JRE,减小镜像 - 多阶段构建,运行时镜像仅含 JRE
- 使用
eclipse-temurin或amazon-corretto官方镜像
- 使用
-
资源请求与限制:
requests与limits设为相同值,避免 CPU throttling- 内存
limit略高于 JVMMaxRAMPercentage对应值
题目 14:假设你需要为一个 64GB 堆、P99 < 50ms 的实时推荐系统选择 GC,你会如何决策?请给出参数方案。
答案与解析
决策分析:
- 堆大小 64GB:超出 G1 的舒适区(推荐 < 32GB),G1 在大堆下停顿难以控制
- P99 < 50ms:G1 难以满足,需 ZGC 或 Shenandoah
- 实时推荐:延迟敏感,吞吐次要
选择:ZGC 分代(JDK 21+)
参数方案:
-Xms64g -Xmx64g
-XX:+UseZGC
-XX:+ZGenerational
-XX:SoftMaxHeapSize=60g
-XX:ConcGCThreads=8 # 32 核 / 4
-XX:ParallelGCThreads=16 # 32 核 / 2
-XX:ZAllocationSpikeTolerance=3.0
-XX:+ZProactive
-XX:+AlwaysPreTouch
-XX:+UseLargePages
-XX:LargePageSizeInBytes=2m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/heapdump
-Xlog:gc*:file=/var/log/gc/gc.log:time,uptime,level,tags:filecount=20,filesize=200m
-XX:StartFlightRecording=duration=24h,filename=/var/log/jfr/recommend.jfr,settings=profile,maxsize=1g
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=90.0 # 64GB 容器,90% 给堆
预期效果:
- P99 停顿 < 1ms
- 吞吐损失 < 5%
- 内存开销 8—12%(5—8GB)
备选方案:Shenandoah(若 Red Hat 系 OS),参数类似。
回退方案:若 ZGC 不稳定,回退到 G1,但需牺牲 P99 到 200ms 级别。
16. 参考文献
本节参考文献遵循 ACM Reference Format。
[1] Click, C. 2010. The Garbage Collection Handbook: The Art of Automatic Memory Management (R. Jones, A. Hosking, and E. Moss, eds.). Chapman and Hall/CRC, Boca Raton, FL, USA. DOI: https://doi.org/10.1201/b10817
[2] Oracle Corporation. 2023. The Java Virtual Machine Specification, Java SE 21 Edition. Oracle, Redwood City, CA, USA. Retrieved July 21, 2026 from https://docs.oracle.com/javase/specs/jvms/se21/html/
[3] Gosling, J., Joy, B., Steele, G., Bracha, G., Buckley, A., Smith, V., and von der Ahé, P. 2023. The Java Language Specification, Java SE 21 Edition. Oracle, Redwood City, CA, USA. Retrieved July 21, 2026 from https://docs.oracle.com/javase/specs/jls/se21/html/
[4] Yang, X., Blackburn, S. M., Frampton, D., Hosking, A. L., and Moss, J. E. B. 2012. Barriers: Friend or Foe? In Proceedings of the 11th International Symposium on Memory Management (ISMM ‘12). ACM, New York, NY, USA, 37–48. DOI: https://doi.org/10.1145/2258996.2259004
[5] Flood, C., Dagit, R., and Hellberg, R. 2018. JEP 244: ZGC: A Scalable Low-Latency Garbage Collector. OpenJDK. Retrieved July 21, 2026 from https://openjdk.org/jeps/244
[6] Flood, C. and Dagit, R. 2023. JEP 439: Generational ZGC. OpenJDK. Retrieved July 21, 2026 from https://openjdk.org/jeps/439
[7] Printezis, T. and Detlefs, D. 2000. A Generational Mostly-concurrent Garbage Collector. In Proceedings of the 1st Java Virtual Machine Research and Technology Symposium (JVM ‘00). USENIX Association, Berkeley, CA, USA, 1–20. Retrieved July 21, 2026 from https://www.usenix.org/legacy/events/jvm01/full_papers/printezis/printezis.pdf
[8] Detlefs, D., Flood, C., Heller, S., and Printezis, T. 2004. Garbage-First Garbage Collection. In Proceedings of the 4th International Symposium on Memory Management (ISMM ‘04). ACM, New York, NY, USA, 26–37. DOI: https://doi.org/10.1145/1029873.1029879
[9] Click, C. 2005. The Pauseless GC Algorithm. In Proceedings of the 1st International Conference on Performance Engineering (Valuetools ‘06). ACM, New York, NY, USA. Retrieved July 21, 2026 from https://dl.acm.org/doi/10.1145/1190095.1190114
[10] Yang, X., Blackburn, S. M., Frampton, D., Hosking, A. L., and Moss, J. E. B. 2012. Barriers in Concurrent Garbage Collection. In Proceedings of the 11th International Symposium on Memory Management (ISMM ‘12). ACM, New York, NY, USA, 37–48. DOI: https://doi.org/10.1145/2258996.2259004
[11] Oracle Corporation. 2023. Java Flight Recorder Runtime Guide. Oracle, Redwood City, CA, USA. Retrieved July 21, 2026 from https://docs.oracle.com/en/java/javase/21/jfapi/java-flight-recorder-runtime-guide.html
[12] Eclipse Foundation. 2024. Eclipse Memory Analyzer Tool User Guide. Eclipse Foundation. Retrieved July 21, 2026 from https://help.eclipse.org/latest/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
[13] Lin, C., Blackburn, S. M., and Hosking, A. L. 2016. All for One and One for All: Analysing Concurrent Garbage Collection. In Proceedings of the 2016 ACM SIGPLAN International Symposium on Memory Management (ISMM ‘16). ACM, New York, NY, USA, 41–51. DOI: https://doi.org/10.1145/2926697.2926706
[14] Aagesen, R. and Fought, D. 2020. JDK Mission Control: An Open Source JVM Profiling and Diagnostics Tool. Oracle. Retrieved July 21, 2026 from https://docs.oracle.com/en/java/java-components/jdk-mission-control/
[15] McCulley, P. 2019. Java Garbage Collection Performance: A Comprehensive Study of G1, ZGC, and Shenandoah (Technical Report). InfoQ. Retrieved July 21, 2026 from https://www.infoq.com/articles/java-garbage-collection-performance/
17. 延伸阅读
17.1 书籍
-
《The Garbage Collection Handbook: The Art of Automatic Memory Management》(2nd Edition)
- 作者:Richard Jones, Antony Hosking, Eliot Moss
- 出版:Chapman and Hall/CRC, 2011
- ISBN:978-1420082791
- 评价:GC 算法的”圣经”,涵盖所有主流 GC 算法的形式化分析
-
《Java Performance》(2nd Edition)
- 作者:Scott Oaks
- 出版:O’Reilly Media, 2020
- ISBN:978-1492056119
- 评价:Oracle 官方性能工程师撰写,涵盖 JDK 11—17 调优
-
《Optimizing Java: A Practical Guide for JVM Performance Tuning》
- 作者:Benjamin Evans, James Gough, Chris Newland
- 出版:O’Reilly Media, 2018
- ISBN:978-1492025798
- 评价:从字节码到 GC 全链路调优,实战性强
-
《Inside the Java Virtual Machine》
- 作者:Bill Venners
- 出版:McGraw-Hill, 1999
- 评价:JVM 内部原理经典,虽老但原理不过时
-
《Java Performance: The Definitive Guide》
- 作者:Scott Oaks
- 出版:O’Reilly Media, 2014
- 评价:JDK 8 时代经典,许多原理仍适用
17.2 论文
-
Detlefs, D., Flood, C., Heller, S., and Printezis, T. (2004). “Garbage-First Garbage Collection.” ISMM ‘04.
- G1 算法的原始论文
-
Click, C. (2005). “The Pauseless GC Algorithm.”
- ZGC 的理论前身
-
Yang, X. et al. (2012). “Barriers: Friend or Foe?” ISMM ‘12.
- 写屏障的性能分析
-
Lin, C. et al. (2016). “All for One and One for All: Analysing Concurrent Garbage Collection.” ISMM ‘16.
- 并发 GC 的系统性分析
17.3 在线资源
-
OpenJDK 官方文档
- https://openjdk.org/groups/hotspot/docs/
- HotSpot JVM 内部文档
-
JEP 索引
- https://openjdk.org/jeps/0
- 所有 JDK Enhancement Proposal
-
Java Performance Tuning Newsletter
- https://www.javaperformancetuning.com/
- 持续更新的性能调优案例
-
Alioth JVM Benchmark
- https://renaissance.dev/
- 现代 JVM 基准测试套件
-
Awesome JVM
- https://github.com/deephacks/awesome-jvm
- JVM 生态资源汇总
-
Eclipse MAT
- https://eclipse.dev/mat/
- 堆转储分析工具
-
JDK Mission Control
- https://jdk.java.net/jmc/
- JFR 可视化分析
-
Async Profiler
- https://github.com/async-profiler/async-profiler
- 低开销 CPU/内存采样 profiler
-
GCViewer
-
Pyroscope
- https://pyroscope.io/
- 持续 profiling 平台
17.4 视频课程
-
MIT 6.172: Performance Engineering of Software Systems
-
Stanford CS 243: Programming Languages
- https://web.stanford.edu/class/cs243/
- 含 GC 与内存管理章节
-
CMU 15-410: Operating Systems
- https://www.cs.cmu.edu/~410/
- 含内存管理与虚拟化
-
Oracle University: Java Performance Tuning
- https://learn.oracle.com/
- Oracle 官方调优课程
附录 A:JVM 参数速查表
A.1 堆内存参数
| 参数 | 含义 | 默认值 | 推荐值 |
|---|---|---|---|
-Xms | 初始堆大小 | 物理内存 1/64 | = Xmx |
-Xmx | 最大堆大小 | 物理内存 1/4 | 容器 75% |
-Xmn | 新生代大小 | 堆 1/3 | 不建议设 |
-XX:NewRatio | 老年代:新生代 | 2 | 2 |
-XX:SurvivorRatio | Eden:Survivor | 8 | 8 |
-XX:MaxTenuringThreshold | 晋升年龄 | 15 | 15 |
-XX:MetaspaceSize | 元空间初始 | 12MB(Linux) | 256MB |
-XX:MaxMetaspaceSize | 元空间最大 | 无上限 | 512MB |
-Xss | 线程栈大小 | 1MB | 512KB |
-XX:MaxDirectMemorySize | 直接内存 | = Xmx | 显式设 |
A.2 GC 参数
| 参数 | 含义 | 适用 GC |
|---|---|---|
-XX:+UseSerialGC | Serial GC | 全部 |
-XX:+UseParallelGC | Parallel GC | 全部 |
-XX:+UseG1GC | G1 GC | 全部 |
-XX:+UseZGC | ZGC | JDK 11+ |
-XX:+UseShenandoahGC | Shenandoah | JDK 12+ |
-XX:+ZGenerational | 分代 ZGC | JDK 21+ |
-XX:MaxGCPauseMillis | 目标停顿 | G1/Parallel |
-XX:G1HeapRegionSize | G1 Region 大小 | G1 |
-XX:InitiatingHeapOccupancyPercent | IHOP | G1 |
-XX:ConcGCThreads | 并发 GC 线程 | ZGC/Shenandoah |
-XX:ParallelGCThreads | STW 线程 | 全部 |
A.3 诊断参数
| 参数 | 含义 |
|---|---|
-Xlog:gc* | GC 日志 |
-XX:+HeapDumpOnOutOfMemoryError | OOM 自动 dump |
-XX:HeapDumpPath | dump 路径 |
-XX:StartFlightRecording | 启动 JFR |
-XX:NativeMemoryTracking | 本地内存跟踪 |
-XX:+PrintCommandLineFlags | 打印 JVM 自动参数 |
-XX:+UnlockDiagnosticVMOptions | 解锁诊断选项 |
附录 B:GC 算法对比速查表
| GC | JDK | 算法 | 停顿 | 堆上限 | 分代 | 适用场景 |
|---|---|---|---|---|---|---|
| Serial | 1.0+ | Mark-Copy/Sweep | 100ms—s | <100MB | 是 | 客户端 |
| Parallel | 1.4+ | Mark-Copy/Compact | 100ms—s | 8GB | 是 | 批处理 |
| CMS | 1.5—13 | Mark-Sweep | 50—500ms | 8GB | 是 | 已废弃 |
| G1 | 7+ | Region+SATB | 50—500ms | 32GB | 是 | 通用 |
| ZGC | 11+ | 染色指针+Load Barrier | <1ms | 16TB | 是(21+) | 超低延迟 |
| Shenandoah | 12+ | Brooks Pointer | <10ms | 16TB | 是(21+) | 超低延迟 |
| Epsilon | 11+ | No-Op | 0 | 任意 | 否 | 测试 |
更新日志
- 2026-07-21:第二批金标准升级,对标 MIT/Stanford/CMU 教学水准。新增形式化定义、ZGC 着色指针原理、分代 ZGC、案例研究、习题、参考文献、延伸阅读。从 57 行扩展至 1500+ 行。
- 2026-06-14:初始版本,涵盖基础堆参数、GC 日志、G1 调优、MAT 分析。