抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

垃圾回收(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; // a引用b
b.next = a; // b引用a
a = null;
b = null;
// 此时a和b互相引用,计数都不为0,但实际上已无法访问

可达性分析

从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、小内存场景 │
│ │
└──────────────────────────────────────────────────────────────────┘

启用方式

1
-XX:+UseSerialGC

适用场景

  • 客户端模式
  • 单核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 # 设置GC线程数

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              # 启用(JDK 8默认)
-XX:MaxGCPauseMillis=N # 最大停顿时间目标
-XX:GCTimeRatio=N # 吞吐量目标(1/(1+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(多线程标记-整理) │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘

启用方式

1
-XX:+UseParallelOldGC    # JDK 8默认启用

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                  # 启用CMS
-XX:CMSInitiatingOccupancyFraction=75 # 老年代使用75%时触发GC
-XX:+UseCMSCompactAtFullCollection # Full GC时整理碎片
-XX:CMSFullGCsBeforeCompaction=5 # 5次Full GC后整理

CMS状态

  • JDK 9标记为废弃
  • JDK 14正式移除

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 # 启用G1(JDK 9+默认)
-XX:MaxGCPauseMillis=200 # 目标最大停顿时间
-XX:G1HeapRegionSize=N # Region大小(1-32MB,2的幂)

# 年轻代参数
-XX:G1NewSizePercent=5 # 年轻代最小占比(默认5%)
-XX:G1MaxNewSizePercent=60 # 年轻代最大占比(默认60%)

# 并发标记参数
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发标记的堆占用率
-XX:G1MixedGCLiveThresholdPercent=85 # Region存活率超过此值不纳入Mixed GC

# Mixed GC参数
-XX:G1MixedGCCountTarget=8 # 一次并发标记后的Mixed GC次数
-XX:G1OldCSetRegionThresholdPercent=10 # Mixed GC中老年代Region占比上限

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
# 启用ZGC
-XX:+UseZGC # JDK 11-14需要解锁实验性功能
-XX:+UnlockExperimentalVMOptions # JDK 11-14

# JDK 21+ 启用分代ZGC
-XX:+UseZGC -XX:+ZGenerational

# 堆大小
-Xms16g -Xmx16g # 建议Xms=Xmx

# 并发GC线程数
-XX:ConcGCThreads=N # 默认为CPU核数的1/8

# 触发GC的堆占用率
-XX:ZCollectionInterval=N # 最大GC间隔(秒),0=禁用
-XX:ZAllocationSpikeTolerance=N # 分配速率容忍度

# 日志
-Xlog:gc* # 打印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
# 启用Shenandoah(OpenJDK)
-XX:+UseShenandoahGC

# 并发GC线程数
-XX:ConcGCThreads=N

# 并行GC线程数(STW阶段)
-XX:ParallelGCThreads=N

# GC启发式策略
-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 # 年轻代大小(G1不建议设置)

# 元空间设置
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间最大大小

# GC日志
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m

# JDK 8 GC日志
-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

选择垃圾回收器的核心原则:先确定需求(吞吐量/延迟/资源),再选择合适的收集器,最后通过监控和调优达到最佳效果