返回博客列表
技术分享
JVM垃圾回收算法详解
2026年01月31日
4 分钟阅读
JVM垃圾回收算法详解
作为Java开发者,理解JVM的垃圾回收机制是提升应用性能的关键。本文将深入探讨JVM中的四大垃圾回收算法,帮助你选择最适合你应用的GC策略。
什么是垃圾回收?
垃圾回收(Garbage Collection,GC)是Java虚拟机自动管理内存的核心机制。它的主要任务包括:
- 识别垃圾对象:找出哪些对象不再被使用
- 回收内存:释放垃圾对象占用的内存空间
- 内存整理:减少内存碎片,提高内存利用率
判断对象是否存活
在讨论GC算法之前,先了解JVM如何判断对象是否存活:
1. 引用计数法(Reference Counting)
每个对象维护一个引用计数器,当有地方引用它时计数器+1,引用失效时-1。当计数器为0时,该对象就是垃圾。
优点: 实现简单,效率高 缺点: 无法解决循环引用问题
2. 可达性分析(Reachability Analysis)
JVM实际采用的方法。从一系列称为"GC Roots"的对象开始,向下搜索,走过的路径称为引用链。如果一个对象没有任何引用链连接到GC Roots,则该对象不可达,视为垃圾。
GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
四大垃圾回收算法
1. 标记-清除算法(Mark-Sweep)
执行过程:
- 标记:标记所有存活对象
- 清除:回收未标记的对象
// 伪代码示例
void markAndSweep() {
// 1. 标记阶段
markReachableObjects(gcRoots);
// 2. 清除阶段
sweepUnmarkedObjects();
}
优点:
- 实现简单
- 不需要移动对象
缺点:
- 会产生大量内存碎片
- 效率不高(两次遍历)
适用场景: 内存碎片对性能影响不大的场景
2. 复制算法(Copying)
执行过程:
- 将内存分为两个相等的区域
- 将存活对象复制到另一个区域
- 清空当前区域
// 伪代码示例
void copyingGC() {
// 1. 复制存活对象
copyReachableObjects(fromSpace, toSpace);
// 2. 交换from和to空间
swapSpaces();
// 3. 清空from空间(已经是新的to空间了)
clearFromSpace();
}
优点:
- 不会产生内存碎片
- 效率高(只需遍历存活对象)
- 简单高效,适合对象存活率低的情况
缺点:
- 内存利用率低(只能用一半)
改进: Eden:Survivor:Survivor = 8:1:1
适用场景: 新生代(对象存活率低)
3. 标记-整理算法(Mark-Compact)
执行过程:
- 标记所有存活对象
- 将存活对象向一端移动
- 清理掉边界外的内存
// 伪代码示例
void markAndCompact() {
// 1. 标记阶段
markReachableObjects(gcRoots);
// 2. 整理阶段(移动存活对象)
compactReachableObjects();
// 3. 清理阶段
clearCompactedSpace();
}
优点:
- 不会产生内存碎片
- 内存利用率高
缺点:
- 移动对象需要额外开销
适用场景: 老年代(对象存活率高)
4. 分代收集算法(Generational Collection)
根据对象存活周期的不同,将内存划分为几块:
┌─────────────────────────────────────┐
│ Young Generation │
│ ┌──────┐ ┌────┐ ┌────┐ │
│ │ Eden │ │ S0 │ │ S1 │ │
│ └──────┘ └────┘ └────┘ │
│ 8:1:1 │
├─────────────────────────────────────┤
│ Old Generation │
├─────────────────────────────────────┤
│ Metaspace │
└─────────────────────────────────────┘
分代策略:
- 新生代:使用复制算法(对象存活率低)
- 老年代:使用标记-整理或标记-清除算法(对象存活率高)
对象晋升规则:
- 新对象分配在Eden区
- Eden区满后,触发Minor GC,存活对象移到Survivor区
- Survivor区满后,继续复制到另一个Survivor区
- 对象年龄达到阈值(默认15),晋升到老年代
- 老年代空间不足时,触发Full GC
常见的垃圾收集器
Serial GC
- 单线程GC
- 适合客户端应用
- STW时间较长
Parallel GC
- 多线程GC
- 吞吐量优先
- 适合后台计算任务
-XX:+UseParallelGC
CMS GC(Concurrent Mark Sweep)
- 低延迟
- 并发标记和清除
- 已被G1 GC取代
G1 GC(Garbage First)
- 面向服务端应用
- 可预测停顿时间
- 将堆划分为多个Region
-XX:+UseG1GC
ZGC / Shenandoah
- 超低延迟
- 几乎无停顿
- 适合对延迟要求极高的应用
性能调优建议
1. 合理设置堆内存大小
-Xms2g -Xmx2g
2. 调整新生代和老年代比例
-XX:NewRatio=2 # 新生代:老年代 = 1:2
3. 设置GC日志
-Xlog:gc*:file=gc.log:time,uptime:level,tags
4. 选择合适的GC收集器
根据应用特点选择:
- 吞吐量优先:Parallel GC
- 低延迟优先:G1 GC / ZGC
- 内存受限:CMS GC(已废弃)
总结
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 标记-清除 | 简单 | 内存碎片 | 内存要求不严格 |
| 复制 | 无碎片、高效 | 内存利用率低 | 新生代 |
| 标记-整理 | 无碎片、内存利用率高 | 移动开销大 | 老年代 |
| 分代收集 | 综合优化 | 配置复杂 | 通用场景 |
最佳实践:
- 优先使用分代收集
- 根据应用特点选择GC收集器
- 定期监控GC日志,优化参数
- 避免手动调用System.gc()
理解垃圾回收算法不仅有助于面试,更重要的是在实际开发中能够做出更合理的内存管理决策,提升应用性能。
希望这篇文章对你有帮助!如果你有任何问题或想要深入讨论某个点,欢迎在评论区交流。