Java 垃圾回收机制详解
垃圾回收是 JVM 自动内存管理的核心机制,理解 GC 原理对性能调优至关重要。
一、为什么需要 GC
1.1 内存管理对比
| 语言 | 内存管理 | 优点 | 缺点 |
|---|---|---|---|
| C/C++ | 手动 malloc/free | 灵活、可控 | 易泄漏、悬空指针 |
| Java | 自动 GC | 安全、省心 | STW、性能开销 |
1.2 垃圾回收目标
- 准确性:不回收存活对象,不遗漏垃圾对象
- 高效性:减少 STW(Stop-The-World)时间
- 低延迟:避免长时间停顿
二、如何判断对象可回收
2.1 引用计数法(已淘汰)
// 对象被引用一次,计数 +1
// 引用失效,计数 -1
// 计数为 0 时可回收
// ❌ 无法解决循环引用
A objA = new A();
B objB = new A();
objA.setB(objB);
objB.setA(objA);
// 即使不再使用,计数也不为 0
2.2 可达性分析(GC Roots)
GC Roots
├── 虚拟机栈引用的对象
├── 静态属性引用的对象
├── 常量引用的对象
└── 本地方法栈引用的对象(JNI)
算法流程:
从 GC Roots 向下搜索
↓
可达的对象 → 存活
不可达的对象 → 可回收
三、垃圾回收算法
3.1 标记 - 清除(Mark-Sweep)
flowchart LR
A[标记阶段] --> B[清除阶段]
B --> C[产生碎片]
特点:
- ✅ 简单高效
- ❌ 效率不高(标记 + 清除)
- ❌ 内存碎片
3.2 标记 - 复制(Copying)
新生代内存
├── Eden 区(80%)
├── Survivor0 区(10%)
└── Survivor1 区(10%)
GC 时:
Eden + Survivor0 → Survivor1(存活对象)
然后清空 Eden 和 Survivor0
特点:
- ✅ 无碎片
- ✅ 效率高(只复制存活对象)
- ❌ 内存利用率低(一半闲置)
3.3 标记 - 整理(Mark-Compact)
标记存活对象
↓
向一端移动
↓
清理边界外内存
特点:
- ✅ 无碎片
- ✅ 内存利用率高
- ❌ 移动对象成本高
3.4 分代收集(主流)
堆内存分代
├── 新生代(Young)
│ ├── 对象朝生夕死
│ └── 复制算法
└── 老年代(Old)
├── 对象存活率高
└── 标记 - 整理算法
四、垃圾收集器
4.1 收集器对比
graph LR
subgraph 新生代
A[Serial]
B[ParNew]
C[Parallel Scavenge]
end
subgraph 老年代
D[Serial Old]
E[CMS]
F[Parallel Old]
G[G1]
end
A --> D
B --> E
C --> F
4.2 Serial 收集器
单线程收集
├── 简单高效(单 CPU 首选)
└── STW 时间长
适用场景:客户端应用、内存较小
4.3 ParNew 收集器
Serial 的多线程版本
├── 多线程并行
└── 需要多 CPU 支持
适用场景:配合 CMS 使用
4.4 Parallel Scavenge
关注吞吐量
├── 自适应调节策略
└── -XX:MaxGCPauseMillis 设置最大停顿
适用场景:后台运算、科学计算
4.5 CMS(Concurrent Mark Sweep)
并发收集
├── 初始标记(STW)
├── 并发标记
├── 重新标记(STW)
└── 并发清除
特点:
- ✅ 并发收集、低停顿
- ❌ 内存碎片
- ❌ CPU 敏感
- ❌ 浮动垃圾
适用场景:B/S 架构、重视响应速度
4.6 G1(Garbage First)
Region 分区
├── Eden / Survivor / Old / Humongous
└── 可预测停顿时间模型
特点:
- ✅ 空间整合(无碎片)
- ✅ 可预测停顿
- ✅ 大堆支持(6GB+)
- ❌ 内存占用大
适用场景:服务端、大内存、低延迟
五、GC 日志分析
5.1 开启 GC 日志
# JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc.log
# JDK 9+
-Xlog:gc*:file=gc.log:time,uptime,level,tags
5.2 日志解读
[GC (Allocation Failure)
[PSYoungGen: 1024K->256K(2048K)] // 新生代回收
1536K->768K(4096K), // 堆整体回收
0.0012345 secs] // 耗时
5.3 常用工具
| 工具 | 作用 |
|---|---|
| jstat | 实时查看 GC 统计 |
| GCViewer | 可视化分析 GC 日志 |
| GCEasy | 在线分析工具 |
| MAT | 内存泄漏分析 |
六、调优参数
6.1 基础参数
# 堆大小
-Xms2g -Xmx2g # 固定堆大小(避免动态调整)
-Xmn512m # 新生代大小
# 收集器选择
-XX:+UseG1GC # 使用 G1
-XX:+UseConcMarkSweepGC # 使用 CMS
# 最大停顿时间
-XX:MaxGCPauseMillis=200 # 目标最大停顿(G1)
6.2 高级参数
# 晋升年龄
-XX:MaxTenuringThreshold=15 # 最大晋升年龄
# 大对象直接进入老年代
-XX:PretenureSizeThreshold=1m
# 元空间
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
七、调优实践
7.1 调优目标
高吞吐量:最大化用户线程执行时间
低延迟:最小化 GC 停顿时间
平衡:根据业务需求权衡
7.2 调优步骤
1. 监控 → 2. 分析 → 3. 调整 → 4. 验证 → 5. 迭代
7.3 常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 频繁 Minor GC | 新生代过小 | 增大 -Xmn |
| Full GC 频繁 | 老年代不足/内存泄漏 | 增大 -Xmx,排查泄漏 |
| GC 时间过长 | 堆过大/对象过多 | 优化代码,调整 Region |
| OOM | 内存不足 | 增大堆,排查泄漏 |
八、总结
GC 核心要点:
| 算法 | 特点 | 适用 |
|---|---|---|
| 标记 - 清除 | 简单、有碎片 | 不单独使用 |
| 标记 - 复制 | 高效、无碎片 | 新生代 |
| 标记 - 整理 | 无碎片、成本高 | 老年代 |
| 收集器 | 特点 | 场景 |
|---|---|---|
| G1 | 可预测停顿 | 大堆、低延迟 |
| CMS | 并发收集 | 低停顿、小堆 |
| Parallel | 高吞吐 | 后台计算 |
GC 调优是系统工程,需要结合业务场景、监控数据持续优化。