前置知识: Java

JVM调优

68 minAdvanced2026/7/21

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. 学习目标

完成本章学习后,学习者应能够:

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 核心能力指标

完成本章后,应能独立完成以下任务:

  1. 为一个日均千万级请求的 Spring Boot 服务设计 JVM 参数方案
  2. 使用 jcmdjstatjmapjfr 完成生产环境性能诊断
  3. 解读 G1 GC 日志中的 ToSpace Exhausted、Evacuation Failure 等异常
  4. 使用 Eclipse MAT 定位内存泄漏,生成 Dominator Tree 与 Leak Suspects 报告
  5. 评估并选择 ZGC、Shenandoah、G1 三者中适合特定 SLA 的收集器

1.3 前置知识检查

阅读本章前,建议已掌握:

  • JVM 内存模型(Heap、Metaspace、Stack、PC Register)
  • Java 字节码基础(javap -c 能读懂)
  • 操作系统进程/线程、虚拟内存概念
  • 基本的 Linux 命令(topvmstatiostatstrace

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 演进背后是三类工作负载的推动:

  1. 吞吐优先(1995—2010):批处理、HPC 场景,Parallel GC 主导。代表:Hadoop、Cassandra。
  2. 延迟优先(2010—2020):金融交易、实时推荐,CMS→G1 主导。代表:高频交易、广告投放。
  3. 超低延迟与超大堆(2020—至今):云原生、内存计算,ZGC/Shenandoah 主导。代表:SAP HANA、Netflix 缓存层。

2.3 调优方法的范式转移

时代方法论代表工具核心理念
1995—2010经验主义jstatjmap凭经验调 -Xmx,重启即生效
2010—2018数据驱动GCViewer、-Xloggc量化停顿、吞吐、频率
2018—2023可观测性JFR、Async-Profiler、Pyroscope持续监控,APM 全链路
2023—至今自适应与 AIJFR 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 是实现细节,规范只要求:

  1. 不可见对象(unreachable object)最终被回收
  2. finalize() 在对象被回收前调用一次(Java 9 起 deprecated)
  3. System.gc() 是”建议”而非”命令”

3.2 内存分区的形式化模型

MM 为 JVM 运行时总内存,则:

M=Mheap+Mmetaspace+Mstack+Mcode+Mdirect+MinternalM = M_{\text{heap}} + M_{\text{metaspace}} + M_{\text{stack}} + M_{\text{code}} + M_{\text{direct}} + M_{\text{internal}}

其中:

  • MheapM_{\text{heap}}:堆内存,受 -Xms-Xmx 约束
  • MmetaspaceM_{\text{metaspace}}:元空间,受 -XX:MetaspaceSize-XX:MaxMetaspaceSize 约束
  • MstackM_{\text{stack}}:线程栈总和,Mstack=Nthreads×XssM_{\text{stack}} = N_{\text{threads}} \times \text{Xss}
  • McodeM_{\text{code}}:Code Cache,JIT 编译后的机器码
  • MdirectM_{\text{direct}}:Direct ByteBuffer,堆外内存
  • MinternalM_{\text{internal}}:GC 内部数据结构、JVM 自身开销

3.3 分代假说的形式化表述

Weak Generational Hypothesis(弱分代假说):

P(death at age a)λeλa,λ1P(\text{death at age } a) \approx \lambda e^{-\lambda a}, \quad \lambda \gg 1

绝大多数对象朝生夕灭,存活时间服从指数衰减。这导致:

Young dieYoung liveOld dieOld live\frac{|\text{Young die}|}{|\text{Young live}|} \gg \frac{|\text{Old die}|}{|\text{Old live}|}

因此将堆分为新生代(Young)与老年代(Old),对新生代频繁回收、对老年代低频回收,可最小化总停顿时间。

3.4 GC 停顿的形式化定义

设一次 GC 事件 ee 由以下阶段构成:

e=(tinit-mark,troot-scan,theap-scan,tremark,tevac,trefine)e = (t_{\text{init-mark}}, t_{\text{root-scan}}, t_{\text{heap-scan}}, t_{\text{remark}}, t_{\text{evac}}, t_{\text{refine}})

其中 STW(Stop-The-World)阶段为:

STW(e)=tinit-mark+tremark+tevacSTW\text{STW}(e) = t_{\text{init-mark}} + t_{\text{remark}} + t_{\text{evac}}^{\text{STW}}

GC 调优的核心目标即最小化 STW(e)\text{STW}(e),使其满足 SLA:

P(STW(e)TSLA)1ϵP(\text{STW}(e) \leq T_{\text{SLA}}) \geq 1 - \epsilon

其中 TSLAT_{\text{SLA}} 为延迟目标(如 200ms),ϵ\epsilon 为可接受违约概率(如 0.01)。


4. 理论推导与原理解析

4.1 GC 算法复杂度分析

4.1.1 标记-清除(Mark-Sweep)

  • 时间复杂度O(L)O(L),其中 LL 为存活对象数 + 待回收对象数
  • 空间开销O(1)O(1) 额外(位图)
  • 碎片:高,需额外的 compact 过程

4.1.2 标记-复制(Mark-Copy)

  • 时间复杂度O(Llive)O(L_{\text{live}}),仅扫描存活对象
  • 空间开销O(region)O(|\text{region}|),需要 to-space
  • 碎片:无

代价是空间利用率 50%(半区复制)或 Region 级别复制。

4.1.3 标记-整理(Mark-Compact)

  • 时间复杂度O(LlogL)O(L \log L)(需排序)或 O(2L)O(2L)(两次扫描)
  • 空间开销O(1)O(1)
  • 碎片:无

4.1.4 G1 Region-based 模型

G1 将堆划分为 NN 个等大小 Region(默认 1MBR32MB1\text{MB} \leq R \leq 32\text{MB}),每个 Region 动态归属 Eden、Survivor、Old、Humongous。

回收价值预测(Pause Prediction Model):

value(r)=garbage(r)cost(r)\text{value}(r) = \frac{\text{garbage}(r)}{\text{cost}(r)}

G1 在每次 GC 前选择 argmaxvalue(r)\arg\max \sum \text{value}(r),使得 cost(r)Tpause target\sum \text{cost}(r) \leq T_{\text{pause target}}。这是一个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 漏标条件

并发标记中,若同时满足:

  1. 黑色对象新增到白色对象的引用
  2. 灰色对象到白色对象的引用断开

则该白色对象被”漏标”,导致存活对象被误回收。

4.3.3 解决方案

方案代表原理
Incremental UpdateCMS黑色→白色引用时,把黑色变灰,重新标记
SATB(Snapshot At The Beginning)G1标记开始时快照,引用断开时把白色记为灰色,重新标记
** Brooks Pointer / Load Barrier**ZGC每次读引用时检查颜色,错则修正

4.3.4 SATB 形式化

G0G_0 为 GC 开始时的对象图,则 SATB 保证回收的对象集为:

Collect(G0)=Unreachable(G0)NewAllocated(t>t0)\text{Collect}(G_0) = \text{Unreachable}(G_0) \setminus \text{NewAllocated}(t > t_0)

即”回收 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—2GBG1-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=100
中型服务(4—16GB)4—16GBG1-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8m
大型服务(>16GB)16—64GBZGC-XX:+UseZGC -XX:+ZGenerational -Xmx32g
批处理/HPC任意Parallel-XX:+UseParallelGC -XX:MaxGCPauseMillis=5000 -XX:GCTimeRatio=99
超低延迟(<10ms SLA)<32GBZGC/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+cpuGC CPU 占用
gc+ergo自适应决策
gc+taskGC 任务调度
gc+phasesGC 阶段细节
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 停顿最大停顿
4GB0.05ms0.15ms0.3ms
16GB0.08ms0.18ms0.5ms
64GB0.12ms0.25ms0.8ms
256GB0.15ms0.35ms1.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。

诊断步骤

  1. 查看 GC 日志:

    GC(1023) Pause Young (Normal) (G1 Evacuation Pause) 3200M->2100M(4096M) 412ms

    堆使用率 78%,停顿超目标。

  2. 查看年龄分布:

    [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 压力大。

  3. 查看 Region 大小:

    jcmd <pid> GC.heap_info
    # 输出: garbage-first heap 4096M, region size 2M

    Region 太小(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 不足,存活对象无法复制。

处理

  1. 增大堆:-Xmx8g
  2. 增大预留:-XX:G1ReservePercent=20
  3. 提前并发标记:-XX:InitiatingHeapOccupancyPercent=30
  4. 增加混合回收频率:-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 基准

指标G1ZGC(非分代)ZGC(分代,JDK 21)
P50 停顿45ms0.2ms0.15ms
P99 停顿180ms0.8ms0.4ms
Max 停顿520ms1.5ms0.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 CLRGo runtimeV8
GC 算法分代 + Region并发 + 着色指针分代 + SOH/LOH并发三色标记分代 + Orinoco
停顿目标100—500ms<1ms10—100ms<10ms1—50ms
堆上限TB 级16TB数十 GB数百 GBGB 级
分代是(JDK 21+)
并发标记部分部分
并发移动
写屏障Card TableLoad BarrierCard TableHybridDijkstra + 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 最佳实践清单

  1. 生产环境 Xms = Xmx,配合 -XX:+AlwaysPreTouch
  2. 优先使用 G1(JDK 17+),延迟敏感场景用 ZGC
  3. 禁用 System.gc()-XX:+DisableExplicitGC
  4. 开启 OOM 自动 dump-XX:+HeapDumpOnOutOfMemoryError
  5. 统一日志-Xlog:gc* 替代旧 -XX:+PrintGCDetails
  6. JFR 持续监控:开销 <1%,生产必备
  7. 容器感知-XX:+UseContainerSupport -XX:MaxRAMPercentage=75
  8. 大堆用大页-XX:+UseLargePages
  9. 避免 finalize(),用 CleanerAutoCloseable
  10. 定期压测:每次发布前用 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

诊断过程

  1. GC 日志分析

    GC(1024) Pause Young (Normal) (G1 Evacuation Pause) 4500M->3800M(5120M) 820ms
    [warning][gc] GC(1024) to-space exhausted

    堆几乎满,Evacuation Failure。

  2. MAT 分析

    Dominator Tree 显示 ConcurrentHashMap 占用 2.8GB,内部为 Order 对象。

  3. 代码定位

    // 订单缓存未设 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.2s180ms
Young GC 停顿800ms90ms
OOM Kill 频率每日 2—3 次0
堆使用率95%65%

14.2 案例二:Kafka 消费者内存泄漏

业务:Kafka 消费者,每秒处理 10 万条消息,处理 24 小时后 OOM

诊断

  1. JFR 显示 byte[] 持续增长
  2. 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

诊断

  1. jstat -gcutil 显示 Old 区快速上涨
  2. 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 完成以下诊断任务:

  1. 查看 PID 1234 的 JVM 参数
  2. 生成堆 dump 到 /tmp/heap.hprof
  3. 启动 5 分钟 JFR 录制
  4. 查看类直方图
答案与解析
# 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 的停顿与堆大小无关,核心原因:

  1. STW 阶段极短:ZGC 的 STW 仅包括初始标记和再标记,这两个阶段只扫描 GC Roots,与堆大小无关(GC Roots 数量固定,约几千个)。

  2. 并发移动:对象移动(relocation)是并发进行的,应用线程与 GC 线程同时工作。

  3. 着色指针:ZGC 在 64 位指针的高 4 位编码颜色(Marked0、Marked1、Remapped、Finalizable)。移动对象后,旧指针”过期”,但 ZGC 不立即更新所有引用,而是通过颜色标记。

  4. Load Barrier:每次应用线程读取对象引用时,Load Barrier 检查指针颜色:

    • 若颜色正确(指向最新地址),直接返回
    • 若颜色不正确(对象已移动),走慢路径修正指针并返回新地址

这种”惰性修正”使得 GC 不需 STW 扫描全堆更新引用,停顿仅取决于 GC Roots 扫描时间(通常 < 1ms)。

数学上,ZGC 的停顿为:

TpauseZGC=Troot-scan+TremarkO(roots)T_{\text{pause}}^{\text{ZGC}} = T_{\text{root-scan}} + T_{\text{remark}} \approx O(|\text{roots}|)

而传统 GC 的停顿为:

TpauseG1=Troot-scan+TevacO(live objects)T_{\text{pause}}^{\text{G1}} = T_{\text{root-scan}} + T_{\text{evac}} \approx O(|\text{live objects}|)

因此 ZGC 停顿与堆大小无关。

题目 13:在容器化环境(Docker/Kubernetes)中,JVM 调优有哪些特殊考虑?

答案与解析

容器化环境 JVM 调优的关键考虑:

  1. 容器感知

    • JDK 10+ 默认开启 -XX:+UseContainerSupport,JVM 读取 cgroup 内存/CPU 限制
    • 使用 -XX:MaxRAMPercentage 而非 -Xmx,适配不同规格 Pod
    • 避免使用 -Xmx 硬编码,因为 Pod 规格可能变化
  2. 内存分配

    • 容器内存 = JVM 堆 + Metaspace + 堆外 + JVM 自身 + OS 缓存
    • 推荐 -XX:MaxRAMPercentage=75.0,留 25% 给非堆内存
    • Direct Memory(Netty、NIO)需单独限制
  3. CPU 限制

    • JVM GC 线程数默认基于 CPU 核数,容器内可能过多
    • 显式设置 -XX:ParallelGCThreads-XX:ConcGCThreads
    • 虚拟线程(JDK 21)可缓解 IO 密集场景的 CPU 限制
  4. 日志与 dump 持久化

    • GC 日志、heap dump、JFR 需挂载到 PersistentVolume
    • 否则 Pod 重启后丢失
  5. 健康检查

    • Liveness probe 用 /actuator/health/liveness,避免 GC 停顿导致误杀
    • initialDelaySeconds 设为应用启动时间 + 缓冲
    • Readiness probe 区分”启动中”和”不可用”
  6. 优雅停机

    • Spring Boot 配置 server.shutdown=graceful
    • Kubernetes terminationGracePeriodSeconds > 应用 graceful shutdown 时间
    • 避免正在处理的请求被截断
  7. 镜像优化

    • 使用 jlink 生成定制 JRE,减小镜像
    • 多阶段构建,运行时镜像仅含 JRE
    • 使用 eclipse-temurinamazon-corretto 官方镜像
  8. 资源请求与限制

    • requestslimits 设为相同值,避免 CPU throttling
    • 内存 limit 略高于 JVM MaxRAMPercentage 对应值

题目 14:假设你需要为一个 64GB 堆、P99 < 50ms 的实时推荐系统选择 GC,你会如何决策?请给出参数方案。

答案与解析

决策分析

  1. 堆大小 64GB:超出 G1 的舒适区(推荐 < 32GB),G1 在大堆下停顿难以控制
  2. P99 < 50ms:G1 难以满足,需 ZGC 或 Shenandoah
  3. 实时推荐:延迟敏感,吞吐次要

选择: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 书籍

  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 算法的形式化分析
  2. 《Java Performance》(2nd Edition)

    • 作者:Scott Oaks
    • 出版:O’Reilly Media, 2020
    • ISBN:978-1492056119
    • 评价:Oracle 官方性能工程师撰写,涵盖 JDK 11—17 调优
  3. 《Optimizing Java: A Practical Guide for JVM Performance Tuning》

    • 作者:Benjamin Evans, James Gough, Chris Newland
    • 出版:O’Reilly Media, 2018
    • ISBN:978-1492025798
    • 评价:从字节码到 GC 全链路调优,实战性强
  4. 《Inside the Java Virtual Machine》

    • 作者:Bill Venners
    • 出版:McGraw-Hill, 1999
    • 评价:JVM 内部原理经典,虽老但原理不过时
  5. 《Java Performance: The Definitive Guide》

    • 作者:Scott Oaks
    • 出版:O’Reilly Media, 2014
    • 评价:JDK 8 时代经典,许多原理仍适用

17.2 论文

  1. Detlefs, D., Flood, C., Heller, S., and Printezis, T. (2004). “Garbage-First Garbage Collection.” ISMM ‘04.

    • G1 算法的原始论文
  2. Click, C. (2005). “The Pauseless GC Algorithm.”

    • ZGC 的理论前身
  3. Yang, X. et al. (2012). “Barriers: Friend or Foe?” ISMM ‘12.

    • 写屏障的性能分析
  4. Lin, C. et al. (2016). “All for One and One for All: Analysing Concurrent Garbage Collection.” ISMM ‘16.

    • 并发 GC 的系统性分析

17.3 在线资源

  1. OpenJDK 官方文档

  2. JEP 索引

  3. Java Performance Tuning Newsletter

  4. Alioth JVM Benchmark

  5. Awesome JVM

  6. Eclipse MAT

  7. JDK Mission Control

  8. Async Profiler

  9. GCViewer

  10. Pyroscope

17.4 视频课程

  1. MIT 6.172: Performance Engineering of Software Systems

  2. Stanford CS 243: Programming Languages

  3. CMU 15-410: Operating Systems

  4. Oracle University: Java Performance Tuning


附录 A:JVM 参数速查表

A.1 堆内存参数

参数含义默认值推荐值
-Xms初始堆大小物理内存 1/64= Xmx
-Xmx最大堆大小物理内存 1/4容器 75%
-Xmn新生代大小堆 1/3不建议设
-XX:NewRatio老年代:新生代22
-XX:SurvivorRatioEden:Survivor88
-XX:MaxTenuringThreshold晋升年龄1515
-XX:MetaspaceSize元空间初始12MB(Linux)256MB
-XX:MaxMetaspaceSize元空间最大无上限512MB
-Xss线程栈大小1MB512KB
-XX:MaxDirectMemorySize直接内存= Xmx显式设

A.2 GC 参数

参数含义适用 GC
-XX:+UseSerialGCSerial GC全部
-XX:+UseParallelGCParallel GC全部
-XX:+UseG1GCG1 GC全部
-XX:+UseZGCZGCJDK 11+
-XX:+UseShenandoahGCShenandoahJDK 12+
-XX:+ZGenerational分代 ZGCJDK 21+
-XX:MaxGCPauseMillis目标停顿G1/Parallel
-XX:G1HeapRegionSizeG1 Region 大小G1
-XX:InitiatingHeapOccupancyPercentIHOPG1
-XX:ConcGCThreads并发 GC 线程ZGC/Shenandoah
-XX:ParallelGCThreadsSTW 线程全部

A.3 诊断参数

参数含义
-Xlog:gc*GC 日志
-XX:+HeapDumpOnOutOfMemoryErrorOOM 自动 dump
-XX:HeapDumpPathdump 路径
-XX:StartFlightRecording启动 JFR
-XX:NativeMemoryTracking本地内存跟踪
-XX:+PrintCommandLineFlags打印 JVM 自动参数
-XX:+UnlockDiagnosticVMOptions解锁诊断选项

附录 B:GC 算法对比速查表

GCJDK算法停顿堆上限分代适用场景
Serial1.0+Mark-Copy/Sweep100ms—s<100MB客户端
Parallel1.4+Mark-Copy/Compact100ms—s8GB批处理
CMS1.5—13Mark-Sweep50—500ms8GB已废弃
G17+Region+SATB50—500ms32GB通用
ZGC11+染色指针+Load Barrier<1ms16TB是(21+)超低延迟
Shenandoah12+Brooks Pointer<10ms16TB是(21+)超低延迟
Epsilon11+No-Op0任意测试

更新日志

  • 2026-07-21:第二批金标准升级,对标 MIT/Stanford/CMU 教学水准。新增形式化定义、ZGC 着色指针原理、分代 ZGC、案例研究、习题、参考文献、延伸阅读。从 57 行扩展至 1500+ 行。
  • 2026-06-14:初始版本,涵盖基础堆参数、GC 日志、G1 调优、MAT 分析。
返回入门指南