【Java JVM】全面解析Java垃圾回收器——从Serial到ZGC
垃圾回收(Garbage Collection,GC)是JVM自动管理内存的核心机制。从JDK 1.0到JDK 21,Java引入了多种垃圾回收器,各有优劣。本文全面介绍各种GC的原理、特点和使用场景。
垃圾回收基础 什么是垃圾回收 垃圾回收是指JVM自动回收不再使用的对象所占用的内存空间,程序员无需手动释放内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌─────────────────────────────────────────────────────────────────┐ │ JVM内存结构(简化) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 堆(Heap) │ │ │ │ ┌─────────────────────┐ ┌───────────────────────────┐ │ │ │ │ │ 年轻代 │ │ 老年代 │ │ │ │ │ │ ┌─────┬─────┬────┐ │ │ │ │ │ │ │ │ │Eden │ S0 │ S1 │ │ │ Old Generation │ │ │ │ │ │ └─────┴─────┴────┘ │ │ │ │ │ │ │ └─────────────────────┘ └───────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────┐ ┌───────────────────────────────┐ │ │ │ 方法区/元空间 │ │ 栈 │ │ │ └─────────────────────┘ └───────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘
判断对象存活 引用计数法 给对象添加引用计数器,每当有引用指向它时计数加1,引用失效时减1。计数为0的对象可被回收。
缺点 :无法解决循环引用问题。
1 2 3 4 5 6 7 8 9 10 11 12 class Node { Node next; } Node a = new Node ();Node b = new Node ();a.next = b; b.next = a; a = null ; b = null ;
可达性分析 从GC Roots出发,通过引用链遍历所有可达对象,不可达的对象可被回收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌─────────────────────────────────────────────────────────────────┐ │ 可达性分析 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ GC Roots │ │ ┌──────┐ │ │ │栈引用 │────────▶ Object A ────▶ Object B │ │ └──────┘ │ │ │ ┌──────┐ │ │ │ │静态变量│────────▶ Object C ────▶ Object D │ │ └──────┘ │ │ ┌──────┐ │ │ │JNI引用│────────▶ Object E │ │ └──────┘ │ │ │ │ Object F ────▶ Object G ← 不可达,可回收│ │ │ └─────────────────────────────────────────────────────────────────┘
GC Roots包括 :
虚拟机栈中引用的对象
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
同步锁持有的对象
垃圾回收算法 标记-清除(Mark-Sweep) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌──────────────────────────────────────────────────────────────────┐ │ 标记-清除算法 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 回收前: │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ A │ B │ C │ D │ E │ F │ G │ H │ I │ J │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ ✓ ✗ ✓ ✗ ✓ ✗ ✓ ✗ ✓ ✗ (✗=垃圾) │ │ │ │ 标记后清除: │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ A │ │ C │ │ E │ │ G │ │ I │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ ↑ ↑ ↑ ↑ ↑ │ │ └───────┴───────┴───────┴───────┘ │ │ 内存碎片! │ │ │ └──────────────────────────────────────────────────────────────────┘
优点 :简单缺点 :产生内存碎片
复制算法(Copying) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────────────────┐ │ 复制算法 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ From区(使用中) To区(空闲) │ │ ┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┐ │ │ │ A │ B │ C │ D │ E │ │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┘ │ │ ✓ ✗ ✓ ✗ ✓ │ │ │ │ 复制存活对象后: │ │ From区(清空) To区(使用中) │ │ ┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┐ │ │ │ │ │ │ │ │ │ A │ C │ E │ │ │ │ │ └───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┘ │ │ 连续内存! │ │ │ └──────────────────────────────────────────────────────────────────┘
优点 :无碎片,分配快缺点 :内存利用率只有50%
标记-整理(Mark-Compact) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────────────────┐ │ 标记-整理算法 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 回收前: │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ A │ B │ C │ D │ E │ F │ G │ H │ I │ J │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ ✓ ✗ ✓ ✗ ✓ ✗ ✓ ✗ ✓ ✗ │ │ │ │ 标记后整理: │ │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ A │ C │ E │ G │ I │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ │ └───────────────────┘ └───────────────────┘ │ │ 存活对象移动到一端 空闲空间连续 │ │ │ └──────────────────────────────────────────────────────────────────┘
优点 :无碎片,内存利用率高缺点 :需要移动对象,效率较低
分代收集 根据对象存活周期将堆分为年轻代和老年代,分别采用不同算法:
年轻代 :对象朝生夕死,使用复制算法
老年代 :对象存活率高,使用标记-清除或标记-整理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──────────────────────────────────────────────────────────────────┐ │ 分代收集 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────┐ ┌──────────────────────────────┐ │ │ │ 年轻代 │ │ 老年代 │ │ │ │ ┌──────┬─────┬─────┐ │ │ │ │ │ │ │ Eden │ S0 │ S1 │ │ │ Old Generation │ │ │ │ │ 80% │ 10% │ 10% │ │ │ │ │ │ │ └──────┴─────┴─────┘ │ │ │ │ │ │ 复制算法 │ │ 标记-整理/标记-清除 │ │ │ │ Minor GC (频繁) │ │ Major GC / Full GC (较少) │ │ │ └─────────────────────────┘ └──────────────────────────────┘ │ │ │ │ 新对象 ──▶ Eden ──存活──▶ S0/S1 ──多次存活──▶ Old │ │ │ └──────────────────────────────────────────────────────────────────┘
经典垃圾回收器 垃圾回收器发展史 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ┌──────────────────────────────────────────────────────────────────┐ │ 垃圾回收器演进史 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ JDK版本 垃圾回收器 │ │ ──────────────────────────────────────────────────── │ │ JDK 1.0 Serial │ │ JDK 1.4 Parallel Scavenge、Serial Old │ │ JDK 5 Parallel Old、CMS │ │ JDK 6 CMS改进 │ │ JDK 7 G1(实验性) │ │ JDK 8 G1改进 │ │ JDK 9 G1成为默认、CMS废弃 │ │ JDK 11 ZGC(实验性)、Epsilon │ │ JDK 12 Shenandoah │ │ JDK 14 CMS移除、ZGC支持macOS/Windows │ │ JDK 15 ZGC、Shenandoah正式可用 │ │ JDK 21 分代ZGC │ │ │ └──────────────────────────────────────────────────────────────────┘
收集器搭配关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ┌──────────────────────────────────────────────────────────────────┐ │ 收集器搭配关系 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 年轻代 老年代 │ │ ┌────────────┐ ┌────────────┐ │ │ │ Serial │───────────────│ Serial Old │ │ │ └────────────┘ ╲ └────────────┘ │ │ │ ╲ │ │ │ │ ╲ │ │ │ ┌────────────┐ ╲ ┌────────────┐ │ │ │ ParNew │──────────────│ CMS │ (已移除) │ │ └────────────┘ ╱ └────────────┘ │ │ │ ╱ │ │ │ ╱ │ │ ┌────────────┐ ╱ ┌────────────┐ │ │ │ Parallel │───────────────│ Parallel │ │ │ │ Scavenge │ │ Old │ │ │ └────────────┘ └────────────┘ │ │ │ │ ┌───────────────────────────────────────────┐ │ │ │ G1 │ │ │ │ (不区分年轻代/老年代) │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────┐ │ │ │ ZGC / Shenandoah │ │ │ │ (Region化,低延迟) │ │ │ └───────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
Serial收集器 最早的垃圾回收器 ,单线程工作,回收时必须Stop The World(STW)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────────────────┐ │ Serial收集器工作过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 应用线程 ───────────┬──────────────────────┬────────────▶ │ │ │ Stop The World │ │ │ │ ┌────────────────┐ │ │ │ GC线程 └──│ Serial GC │──┘ │ │ │ (单线程复制) │ │ │ └────────────────┘ │ │ │ │ 特点: │ │ • 单线程收集 │ │ • 年轻代使用复制算法 │ │ • 简单高效(无线程切换开销) │ │ • 适合单CPU、小内存场景 │ │ │ └──────────────────────────────────────────────────────────────────┘
启用方式 :
适用场景 :
客户端模式
单核CPU服务器
内存较小(几十MB到一两百MB)
Serial Old收集器 Serial的老年代版本,使用标记-整理算法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────────────────┐ │ Serial Old收集器 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 工作过程(Full GC): │ │ │ │ 应用线程 ───────────┬─────────────────────────────┬──────▶ │ │ │ Stop The World │ │ │ │ ┌───────────────────────┐ │ │ │ GC线程 └──│ Serial Old GC │─┘ │ │ │ (单线程标记-整理) │ │ │ └───────────────────────┘ │ │ │ │ 用途: │ │ • Serial收集器的老年代版本 │ │ • CMS收集器的后备方案(Concurrent Mode Failure) │ │ │ └──────────────────────────────────────────────────────────────────┘
ParNew收集器 Serial的多线程版本 ,是CMS收集器的最佳搭档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌──────────────────────────────────────────────────────────────────┐ │ ParNew收集器 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 应用线程 ───────────┬──────────────────────┬────────────▶ │ │ │ Stop The World │ │ │ │ ┌────────────────┐ │ │ │ GC线程1 └──│ │──┘ │ │ GC线程2 │ ParNew GC │ │ │ GC线程3 │ (多线程复制) │ │ │ GC线程4 │ │ │ │ └────────────────┘ │ │ │ │ 特点: │ │ • 多线程收集(默认与CPU核数相同) │ │ • 可与CMS配合使用 │ │ • JDK 9后不再支持单独使用 │ │ │ └──────────────────────────────────────────────────────────────────┘
启用方式 :
1 2 -XX:+UseParNewGC -XX:ParallelGCThreads=N
Parallel Scavenge收集器 关注吞吐量 的收集器,也叫”吞吐量优先收集器”。
1 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC时间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ┌──────────────────────────────────────────────────────────────────┐ │ Parallel Scavenge收集器 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 特点: │ │ • 多线程收集 │ │ • 年轻代使用复制算法 │ │ • 关注吞吐量而非停顿时间 │ │ • 支持自适应调节策略 │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 停顿时间 vs 吞吐量 │ │ │ │ │ │ │ │ 短停顿:每次GC 10ms,但频繁GC,总GC时间长 │ │ │ │ ──┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─▶ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 高吞吐:每次GC 100ms,但GC次数少,总GC时间短 │ │ │ │ ────────┬────────────────┬────▶ │ │ │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
核心参数 :
1 2 3 4 -XX:+UseParallelGC -XX:MaxGCPauseMillis=N -XX:GCTimeRatio=N -XX:+UseAdaptiveSizePolicy
适用场景 :
Parallel Old收集器 Parallel Scavenge的老年代版本,使用标记-整理算法 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ┌──────────────────────────────────────────────────────────────────┐ │ Parallel Old收集器 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ JDK 6之前: │ │ Parallel Scavenge只能搭配Serial Old,老年代成为瓶颈 │ │ │ │ JDK 6之后: │ │ Parallel Scavenge + Parallel Old = "吞吐量优先组合" │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Parallel组合 │ │ │ │ │ │ │ │ 年轻代:Parallel Scavenge(多线程复制) │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 老年代:Parallel Old(多线程标记-整理) │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
启用方式 :
CMS收集器(已移除) CMS(Concurrent Mark Sweep)是第一款并发收集器 ,以最短停顿时间为目标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──────────────────────────────────────────────────────────────────┐ │ CMS收集器工作过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 阶段 应用线程 GC线程 说明 │ │ ─────────────────────────────────────────────────────────────── │ │ │ │ 1.初始标记 ──┬── ──────── STW,标记GC Roots直接 │ │ (STW) │ 关联的对象(很快) │ │ │ │ │ 2.并发标记 ──┼── ════════ 并发,从GC Roots遍历 │ │ │ 整个对象图(耗时) │ │ │ │ │ 3.重新标记 ──┼── ──────── STW,修正并发标记期间 │ │ (STW) │ 变动的对象 │ │ │ │ │ 4.并发清除 ──┴── ════════ 并发,清除垃圾对象 │ │ (不移动存活对象) │ │ │ │ ─── STW阶段(暂停) ═══ 并发阶段(不暂停) │ │ │ └──────────────────────────────────────────────────────────────────┘
CMS的问题 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ┌──────────────────────────────────────────────────────────────────┐ │ CMS的缺点 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 1. CPU资源敏感 │ │ 并发阶段占用CPU,导致应用吞吐量下降 │ │ 默认GC线程数 = (CPU核数 + 3) / 4 │ │ │ │ 2. 浮动垃圾(Floating Garbage) │ │ 并发清除阶段,用户线程产生的新垃圾无法被清理 │ │ 需要预留空间,否则触发Concurrent Mode Failure │ │ │ │ 3. 内存碎片 │ │ 标记-清除算法不整理内存,产生碎片 │ │ 可能触发Full GC进行整理 │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Concurrent Mode Failure │ │ │ │ │ │ │ │ 并发收集时,老年代空间不足以容纳新晋升的对象 │ │ │ │ 触发Serial Old进行Full GC(STW时间很长) │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
参数配置 :
1 2 3 4 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
CMS状态 :
G1收集器 G1(Garbage First)是面向服务端的收集器,兼顾吞吐量和低延迟。
G1设计理念 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ┌──────────────────────────────────────────────────────────────────┐ │ G1设计理念 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 传统分代收集器: │ │ ┌─────────────────────────────┬───────────────────────────────┐│ │ │ 年轻代 │ 老年代 ││ │ │ (连续的内存空间) │ (连续的内存空间) ││ │ └─────────────────────────────┴───────────────────────────────┘│ │ │ │ G1收集器: │ │ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ │ │ E │ O │ S │ E │ O │ H │ E │ O │ S │ O │ E │ O │ │ │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤ │ │ │ O │ E │ O │ S │ O │ H │ O │ E │ O │ E │ O │ S │ │ │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ │ │ │ │ E = Eden S = Survivor O = Old H = Humongous │ │ │ │ 特点: │ │ • 堆划分为大小相等的Region(1MB~32MB) │ │ • Region可以是任意角色(Eden/Survivor/Old/Humongous) │ │ • 不需要连续的分代空间 │ │ • 优先回收垃圾最多的Region(Garbage First) │ │ │ └──────────────────────────────────────────────────────────────────┘
G1的Region 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ┌──────────────────────────────────────────────────────────────────┐ │ G1 Region │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ Region大小:1MB, 2MB, 4MB, 8MB, 16MB, 32MB │ │ 数量:最多2048个Region │ │ │ │ 计算公式:Region大小 = 堆大小 / 2048(向上取整为2的幂) │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Humongous Region │ │ │ │ │ │ │ │ • 存储大于Region 50%的对象 │ │ │ │ • 可以跨越多个连续Region │ │ │ │ • 直接分配在老年代 │ │ │ │ │ │ │ │ ┌──────┬──────┬──────┐ │ │ │ │ │ H │ HC │ HC │ H=Humongous开始 HC=Humongous继续 │ │ │ │ │ start│ cont │ cont │ │ │ │ │ └──────┴──────┴──────┘ │ │ │ │ └─────────────────┘ │ │ │ │ 一个大对象 │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
G1收集过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ┌──────────────────────────────────────────────────────────────────┐ │ G1收集过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 1. Young GC(年轻代收集) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 触发条件:Eden区用满 │ │ │ │ 回收范围:所有Eden和Survivor Region │ │ │ │ 算法:复制算法 │ │ │ │ STW:是 │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ 2. 并发标记周期(Concurrent Marking Cycle) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ a. 初始标记(Initial Mark)- STW │ │ │ │ 标记GC Roots直接可达的对象 │ │ │ │ 借助Young GC完成 │ │ │ │ │ │ │ │ b. 根区域扫描(Root Region Scan)- 并发 │ │ │ │ 扫描Survivor区引用的老年代对象 │ │ │ │ │ │ │ │ c. 并发标记(Concurrent Mark)- 并发 │ │ │ │ 遍历整个堆,标记存活对象 │ │ │ │ │ │ │ │ d. 重新标记(Remark)- STW │ │ │ │ SATB算法处理并发标记期间的变化 │ │ │ │ │ │ │ │ e. 清理(Cleanup)- 部分STW │ │ │ │ 计算各Region的垃圾比例,排序 │ │ │ │ 释放完全空闲的Region │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ 3. Mixed GC(混合收集) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 触发条件:并发标记完成后 │ │ │ │ 回收范围:年轻代 + 部分老年代(垃圾最多的Region) │ │ │ │ 算法:复制算法 │ │ │ │ STW:是 │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
G1的停顿预测模型 G1使用停顿预测模型来控制GC停顿时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ┌──────────────────────────────────────────────────────────────────┐ │ G1停顿预测模型 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ -XX:MaxGCPauseMillis=200 (默认200ms) │ │ │ │ 工作原理: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 1. 记录每个Region的回收耗时(衰减均值) │ │ │ │ 2. 根据目标停顿时间选择回收的Region数量 │ │ │ │ 3. 优先选择回收价值高(垃圾多、耗时短)的Region │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Region回收价值计算 │ │ │ │ │ │ │ │ Region 垃圾量 预计耗时 回收价值 │ │ │ │ ─────────────────────────────────────────── │ │ │ │ Region A 80% 10ms 高 ✓ 优先回收 │ │ │ │ Region B 60% 5ms 高 ✓ 优先回收 │ │ │ │ Region C 90% 30ms 中 │ │ │ │ Region D 30% 3ms 低 │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
G1重要参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=N -XX:G1NewSizePercent=5 -XX:G1MaxNewSizePercent=60 -XX:InitiatingHeapOccupancyPercent=45 -XX:G1MixedGCLiveThresholdPercent=85 -XX:G1MixedGCCountTarget=8 -XX:G1OldCSetRegionThresholdPercent=10
G1适用场景
场景
说明
大堆内存
6GB以上堆内存效果好
低延迟需求
需要可控的停顿时间
堆内存利用率
G1可以自动调整年轻代大小
替代CMS
从CMS迁移到G1
G1 vs CMS
特性
CMS
G1
设计目标
最短停顿时间
可预测的停顿时间
堆布局
传统分代
Region化
算法
标记-清除
标记-整理(Region间复制)
碎片
有
无
浮动垃圾
有
相对较少
并发失败
触发Serial Old
触发Full GC
适用堆大小
中小堆
大堆(6GB+)
JDK状态
已移除
默认收集器(JDK 9+)
ZGC ZGC(Z Garbage Collector)是JDK 11引入的超低延迟 垃圾回收器。
ZGC设计目标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ┌──────────────────────────────────────────────────────────────────┐ │ ZGC设计目标 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ • 停顿时间不超过1ms(JDK 16目标:不超过1ms) │ │ • 停顿时间不随堆大小增加而增加 │ │ • 支持TB级堆内存 │ │ • 停顿时间不随存活对象数量增加而增加 │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 停顿时间对比 │ │ │ │ │ │ │ │ 收集器 典型停顿时间 │ │ │ │ ────────────────────────────────────────────────────── │ │ │ │ Serial 数百ms到数秒 │ │ │ │ Parallel 数十ms到数百ms │ │ │ │ G1 数十ms到200ms │ │ │ │ ZGC <1ms 到 几ms │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
ZGC核心技术 着色指针(Colored Pointers) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ┌──────────────────────────────────────────────────────────────────┐ │ 着色指针 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 64位指针布局(Linux x86_64): │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 高18位(未使用)│ 4位元数据 │ 42位对象地址 │ │ │ │ [63-46] │ [45-42] │ [41-0] │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ │ │ │ Finaliza │ 是否只能通过finalize访问 │ │ │ Remapped │ 是否已重映射 │ │ │ Marked1 │ 标记位1 │ │ │ Marked0 │ 标记位0 │ │ └──────────┘ │ │ │ │ 作用: │ │ • 在指针中存储GC元数据 │ │ • 无需额外内存存储对象标记状态 │ │ • 支持并发操作 │ │ │ │ 限制: │ │ • 只能使用64位系统 │ │ • 可寻址空间:2^42 = 4TB(最大堆内存) │ │ │ └──────────────────────────────────────────────────────────────────┘
读屏障(Load Barrier) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ┌──────────────────────────────────────────────────────────────────┐ │ 读屏障 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 传统GC使用写屏障(Write Barrier): │ │ • 对象引用被写入时触发 │ │ • 用于维护卡表、记忆集等 │ │ │ │ ZGC使用读屏障(Load Barrier): │ │ • 对象引用被读取时触发 │ │ • 检查指针颜色,必要时进行修复 │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 读屏障伪代码 │ │ │ │ │ │ │ │ Object load(Object* ptr) { │ │ │ │ Object obj = *ptr; │ │ │ │ if (is_bad_color(obj)) { // 检查指针颜色 │ │ │ │ obj = relocate(obj); // 对象已被移动 │ │ │ │ *ptr = obj; // 更新引用 │ │ │ │ } │ │ │ │ return obj; │ │ │ │ } │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ 优点: │ │ • 允许对象在并发移动过程中被安全访问 │ │ • 应用线程帮助完成对象重定位 │ │ │ └──────────────────────────────────────────────────────────────────┘
内存多重映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ┌──────────────────────────────────────────────────────────────────┐ │ 内存多重映射 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ZGC将同一块物理内存映射到多个虚拟地址: │ │ │ │ 虚拟地址空间 物理内存 │ │ ┌──────────────────┐ ┌────────────┐ │ │ │ Marked0视图 │──────────┐ │ │ │ │ │ (带Marked0标记) │ │ │ │ │ │ └──────────────────┘ ├──────▶│ 实际堆 │ │ │ ┌──────────────────┐ │ │ 内存 │ │ │ │ Marked1视图 │──────────┤ │ │ │ │ │ (带Marked1标记) │ │ │ │ │ │ └──────────────────┘ │ │ │ │ │ ┌──────────────────┐ │ └────────────┘ │ │ │ Remapped视图 │──────────┘ │ │ │ (带Remapped标记) │ │ │ └──────────────────┘ │ │ │ │ 作用: │ │ • 通过指针地址就能判断对象状态 │ │ • 无需额外存储空间记录对象标记状态 │ │ │ └──────────────────────────────────────────────────────────────────┘
ZGC收集过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ┌──────────────────────────────────────────────────────────────────┐ │ ZGC收集过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 阶段 STW 说明 │ │ ─────────────────────────────────────────────────────────── │ │ │ │ 1. 初始标记 是 标记GC Roots直接引用的对象 │ │ (Pause Mark │ │ Start) 时间:亚毫秒级 │ │ │ │ 2. 并发标记 否 遍历对象图,标记所有存活对象 │ │ (Concurrent │ │ Mark) 与应用线程并发执行 │ │ │ │ 3. 再标记 是 处理并发标记期间的引用变化 │ │ (Pause Mark 使用SATB算法 │ │ End) │ │ 时间:亚毫秒级 │ │ │ │ 4. 并发准备重分配 否 分析Region,确定需要回收的Region │ │ (Concurrent 建立转发表(Forwarding Table) │ │ Prepare for │ │ Relocate) │ │ │ │ 5. 初始重分配 是 重定位GC Roots引用的对象 │ │ (Pause │ │ Relocate │ │ Start) 时间:亚毫秒级 │ │ │ │ 6. 并发重分配 否 移动对象到新Region │ │ (Concurrent 读屏障修复旧引用 │ │ Relocate) 转发表记录新旧地址映射 │ │ │ │ 7. 并发重映射 否 修正所有指向旧地址的引用 │ │ (Concurrent 与下一次GC的并发标记合并执行 │ │ Remap) │ │ │ └──────────────────────────────────────────────────────────────────┘
分代ZGC(JDK 21) JDK 21引入了分代ZGC,进一步优化性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ┌──────────────────────────────────────────────────────────────────┐ │ 分代ZGC(JDK 21) │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 非分代ZGC(JDK 11-20): │ │ • 每次GC扫描整个堆 │ │ • 不区分对象年龄 │ │ • 对于短命对象效率不是最优 │ │ │ │ 分代ZGC(JDK 21+): │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ ┌─────────────────────┐ ┌──────────────────────────────┐│ │ │ │ │ 年轻代 │ │ 老年代 ││ │ │ │ │ │ │ ││ │ │ │ │ 频繁收集 │ │ 较少收集 ││ │ │ │ │ (大多数对象短命) │ │ (存活时间长的对象) ││ │ │ │ └─────────────────────┘ └──────────────────────────────┘│ │ │ │ │ │ │ │ 优势: │ │ │ │ • 年轻代GC更频繁但更快 │ │ │ │ • 减少扫描存活对象的开销 │ │ │ │ • 更好的内存分配效率 │ │ │ │ • 吞吐量提升 │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
ZGC参数配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:+ZGenerational -Xms16g -Xmx16g -XX:ConcGCThreads=N -XX:ZCollectionInterval=N -XX:ZAllocationSpikeTolerance=N -Xlog:gc*
ZGC适用场景
场景
说明
超低延迟
要求毫秒级甚至亚毫秒级停顿
大堆内存
数十GB到TB级堆
响应时间敏感
金融交易、实时系统
一致性延迟
停顿时间需要稳定可预测
Shenandoah GC Shenandoah是Red Hat开发的低延迟收集器,与ZGC目标相似。
Shenandoah特点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──────────────────────────────────────────────────────────────────┐ │ Shenandoah特点 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 与ZGC的对比: │ │ │ │ │ 特性 │ ZGC │ Shenandoah │ │ │ │────────────────│──────────────────│───────────────────│ │ │ │ 开发者 │ Oracle │ Red Hat │ │ │ │ 着色指针 │ 是 │ 否 │ │ │ │ 读屏障 │ 是 │ 是 │ │ │ │ 写屏障 │ 否 │ 是 │ │ │ │ 转发指针 │ 转发表 │ Brooks指针 │ │ │ │ 压缩指针 │ 不支持 │ 支持 │ │ │ │ 最小堆 │ 较大 │ 较小 │ │ │ │ Oracle JDK │ 包含 │ 不包含 │ │ │ │ └──────────────────────────────────────────────────────────────────┘
Brooks指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ┌──────────────────────────────────────────────────────────────────┐ │ Brooks指针 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 每个对象头部添加一个转发指针: │ │ │ │ 对象未移动时: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ ┌─────────────┐ ┌──────────────────────────────────┐ │ │ │ │ │Brooks指针 │─▶│ 对象数据 │ │ │ │ │ │(指向自己) │ │ │ │ │ │ │ └─────────────┘ └──────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 对象移动后: │ │ ┌────────────────────────┐ ┌──────────────────────────┐ │ │ │ 旧对象 │ │ 新对象 │ │ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ │ │ │Brooks指针 │───────┼──────┼▶│Brooks指针 │─┐ │ │ │ │ │(指向新位置) │ │ │ │(指向自己) │ │ │ │ │ │ └─────────────┘ │ │ └─────────────┘ │ │ │ │ │ ┌──────────────────┐ │ │ ┌──────────────┴───────┐ │ │ │ │ │ 旧数据(废弃) │ │ │ │ 对象数据 │ │ │ │ │ └──────────────────┘ │ │ └──────────────────────┘ │ │ │ └────────────────────────┘ └──────────────────────────┘ │ │ │ │ 优点:无需额外的转发表 │ │ 缺点:每个对象增加一个指针开销 │ │ │ └──────────────────────────────────────────────────────────────────┘
Shenandoah收集过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ┌──────────────────────────────────────────────────────────────────┐ │ Shenandoah收集过程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 阶段 STW 说明 │ │ ────────────────────────────────────────────────────────── │ │ │ │ 1. 初始标记 是 标记GC Roots │ │ 2. 并发标记 否 遍历对象图 │ │ 3. 最终标记 是 处理SATB队列 │ │ 4. 并发清理 否 回收完全空闲的Region │ │ 5. 并发疏散 否 复制存活对象到新Region │ │ 6. 初始更新引用 是 准备更新引用 │ │ 7. 并发更新引用 否 更新所有引用到新地址 │ │ 8. 最终更新引用 是 更新GC Roots │ │ 9. 并发清理 否 回收旧Region │ │ │ │ STW阶段总计:4次,每次亚毫秒级 │ │ │ └──────────────────────────────────────────────────────────────────┘
Shenandoah参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -XX:+UseShenandoahGC -XX:ConcGCThreads=N -XX:ParallelGCThreads=N -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCHeuristics=static -XX:ShenandoahGCHeuristics=compact -XX:ShenandoahGCHeuristics=aggressive
Epsilon GC Epsilon是JDK 11引入的**”无操作”垃圾回收器**。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ┌──────────────────────────────────────────────────────────────────┐ │ Epsilon GC │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 特点: │ │ • 只分配内存,不回收内存 │ │ • 堆用完后直接OutOfMemoryError │ │ • 无GC停顿 │ │ │ │ 用途: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 1. 性能测试 │ │ │ │ 测量应用的真实内存分配速率 │ │ │ │ 排除GC对性能测试的干扰 │ │ │ │ │ │ │ │ 2. 短生命周期任务 │ │ │ │ 任务完成前内存足够,无需GC │ │ │ │ 如:容器化的短命任务 │ │ │ │ │ │ │ │ 3. 延迟敏感应用 │ │ │ │ 绝对不能容忍任何GC停顿 │ │ │ │ 在OOM前完成任务并退出 │ │ │ │ │ │ │ │ 4. GC算法研究 │ │ │ │ 作为基准对比其他GC的效果 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ 启用:-XX:+UseEpsilonGC │ │ │ └──────────────────────────────────────────────────────────────────┘
如何选择垃圾回收器 决策树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ┌──────────────────────────────────────────────────────────────────┐ │ GC选择决策树 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 需求分析 │ │ │ │ │ ┌────────────┼────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ 低延迟优先 吞吐量优先 资源受限 │ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴──────┐ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ 极致低延迟 可控延迟 Parallel Serial │ │ (<1ms) (<200ms) │ │ │ │ │ │ ▼ ▼ │ │ ZGC/Shenandoah G1 │ │ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ 详细建议 │ │ │ │ │ │ │ │ 堆大小 延迟要求 推荐收集器 │ │ │ │ ───────────────────────────────────────────────── │ │ │ │ <100MB 不敏感 Serial │ │ │ │ <4GB 不敏感 Parallel │ │ │ │ 4GB-8GB <200ms G1 │ │ │ │ >8GB <200ms G1 │ │ │ │ 任意 <10ms ZGC/Shenandoah │ │ │ │ 任意 <1ms ZGC/Shenandoah │ │ │ │ │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘
JDK版本与默认收集器
JDK版本
默认收集器
说明
JDK 8
Parallel
吞吐量优先
JDK 9+
G1
平衡吞吐量和延迟
JDK 11+
G1
ZGC可选(实验性)
JDK 15+
G1
ZGC/Shenandoah正式可用
JDK 21+
G1
分代ZGC可用
各收集器对比总结
收集器
年轻代算法
老年代算法
特点
适用场景
Serial
复制
标记-整理
单线程,简单
客户端、小内存
Parallel
复制
标记-整理
多线程,吞吐量优先
后台计算、批处理
CMS
-
标记-清除
并发,低停顿
已废弃
G1
复制
复制
Region化,可预测停顿
大堆、平衡需求
ZGC
-
标记-整理
超低停顿,大堆
极低延迟、TB级堆
Shenandoah
-
标记-整理
超低停顿
极低延迟
GC调优实践 常用JVM参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -Xms4g -Xmx4g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xlog:gc*:file=gc.log:time ,uptime ,level,tags:filecount=5,filesize=10m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
GC日志分析 1 2 3 4 5 6 7 [2025-03-08T10:30:45.123+0800][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) [2025-03-08T10:30:45.123+0800][info][gc] GC(12) Using 4 workers of 8 for evacuation [2025-03-08T10:30:45.145+0800][info][gc] GC(12) Eden regions: 51->0(46) [2025-03-08T10:30:45.145+0800][info][gc] GC(12) Survivor regions: 7->8(8) [2025-03-08T10:30:45.145+0800][info][gc] GC(12) Old regions: 23->25 [2025-03-08T10:30:45.145+0800][info][gc] GC(12) Humongous regions: 2->2 [2025-03-08T10:30:45.145+0800][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 326M->140M(512M) 22.456ms
常见调优思路 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ┌──────────────────────────────────────────────────────────────────┐ │ GC调优思路 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 1. 设置合理的堆大小 │ │ • Xms = Xmx(避免堆大小调整) │ │ • 根据应用需求设置,预留足够空间 │ │ │ │ 2. 选择合适的垃圾回收器 │ │ • 低延迟:G1、ZGC、Shenandoah │ │ • 高吞吐:Parallel │ │ │ │ 3. 减少Full GC │ │ • 增大堆/老年代 │ │ • 避免大对象直接进入老年代 │ │ • 调整晋升阈值 │ │ │ │ 4. 减少对象分配 │ │ • 对象池化 │ │ • 避免不必要的装箱 │ │ • 使用StringBuilder而非String拼接 │ │ │ │ 5. 监控与分析 │ │ • 开启GC日志 │ │ • 使用工具分析(GCViewer、GCEasy) │ │ • 关注GC频率、停顿时间、吞吐量 │ │ │ └──────────────────────────────────────────────────────────────────┘
总结
收集器
引入版本
状态
停顿时间
适用场景
Serial
JDK 1.0
可用
长
客户端/小堆
Parallel
JDK 1.4
JDK 8默认
中
吞吐量优先
CMS
JDK 5
JDK 14移除
短
-
G1
JDK 7
JDK 9+默认
可控
通用
ZGC
JDK 11
JDK 15+正式
<1ms
超低延迟/大堆
Shenandoah
JDK 12
JDK 15+正式
<1ms
超低延迟
Epsilon
JDK 11
可用
无
测试/特殊场景
随着Java版本的演进,垃圾回收器不断优化。对于新项目,建议:
JDK 8 :使用默认Parallel或G1
JDK 11+ :使用G1,低延迟场景考虑ZGC
JDK 21+ :使用G1或分代ZGC
选择垃圾回收器的核心原则:先确定需求(吞吐量/延迟/资源),再选择合适的收集器,最后通过监控和调优达到最佳效果 。