JVM 中的垃圾回收算法及垃圾回收器详解
前言
Java 虚拟机(JVM)的自动内存管理机制中,垃圾回收(Garbage Collection, GC)是至关重要的一个环节。它不仅负责释放那些不再被使用的对象所占用的内存空间,还对程序的性能有着重要影响。本文将详细介绍 JVM 中的垃圾回收算法以及各个版本 JDK 对应的垃圾回收器,并探讨它们的工作原理和适用场景。
一、垃圾回收算法
1. 标记-清除(Mark-Sweep)
标记-清除是最基础的垃圾回收算法之一。其工作过程分为两个阶段:
- 标记阶段:遍历所有对象引用,找出哪些对象是存活的。
- 清除阶段:回收未被标记的对象所占用的空间。
优点:
- 实现简单,不需要额外空间。
缺点:
- 效率低,特别是在存在大量对象的情况下。
- 容易产生内存碎片,导致后续大对象分配时出现空间不足的问题。
2. 复制(Copying)
复制算法将可用内存按容量划分为两块,每次只使用其中一块。当这一块内存用完时,就将存活的对象复制到另一块上面,然后把已使用的内存空间一次清理掉。
优点:
- 不会产生内存碎片。
- 实现简单,运行高效。
缺点:
- 内存利用率低,因为需要预留一半的内存用于复制。
3. 标记-整理(Mark-Compact)
标记-整理结合了标记-清除和复制的优点。在标记阶段之后,不是直接清除未标记的对象,而是让所有存活的对象向一端移动,然后清理掉端边界以外的内存。
优点:
- 解决了内存碎片问题。
- 提高了内存利用率。
缺点:
- 移动对象的成本较高。
4. 分代收集(Generational Collection)
现代垃圾回收器通常采用分代收集的思想,根据对象存活周期的不同将其划分成不同的区域进行回收。一般分为年轻代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation,现已改为元空间 Metaspace)。年轻代又细分为 Eden 区和 Survivor 区(S0/S1)。
- 年轻代:新创建的对象首先分配在这里,由于大部分对象生命周期较短,所以这里采用复制算法。
- 老年代:经过几次垃圾回收后仍然存活的对象会被晋升到这里,采用标记-清除或标记-整理算法。
- 元空间:存储类的元数据等信息,从 JDK8 开始,永久代被移除,改用本地内存实现的元空间替代。
二、垃圾回收器
随着 JDK 版本的发展,JVM 引入了多种垃圾回收器,每种都有其特定的应用场景和优化方向。
1. Serial 收集器
Serial 收集器是最基本的新生代垃圾收集器,单线程执行,适用于单核处理器或小型应用。
- JDK 版本:自 JDK1.3 以来一直存在。
- 特点:简单高效,适合于客户端模式下的应用。
2. Parallel/Throughput 收集器
Parallel 收集器也是一款针对新生代的收集器,但它可以使用多线程并行执行,提高了垃圾回收效率,适合于多核服务器环境。
- JDK 版本:从 JDK6 开始成为默认的服务器端收集器。
- 特点:追求高吞吐量,减少 GC 停顿时间。
3. CMS(Concurrent Mark Sweep)收集器
CMS 是一种以获取最短回收停顿时间为目标的老年代垃圾收集器,通过并发方式执行大部分工作,减少了应用程序的停顿时间。
- JDK 版本:JDK5 引入,JDK9 中被标记为废弃。
- 特点:低延迟,但可能会导致碎片化问题,且 CPU 资源消耗较大。
4. G1(Garbage First)收集器
G1 是面向服务端应用的垃圾收集器,旨在替代 CMS。它将堆划分为多个大小相等的独立区域(Region),跟踪每个 Region 里垃圾的数量,优先回收价值最大的 Region。
- JDK 版本:JDK7u4 作为实验性功能推出,JDK9 成为默认收集器。
- 特点:预测停顿时间模型,可配置最大停顿时间目标;减少了 Full GC 的发生频率。
5. ZGC(Z Garbage Collector)
ZGC 是一个可扩展的低延迟垃圾收集器,设计目标是在任意堆大小下都能提供不超过 10 毫秒的暂停时间。
- JDK 版本:JDK11 作为实验特性引入,JDK15 正式发布。
- 特点:非常低的停顿时间,支持非常大的堆(TB 级别)。
6. Shenandoah
Shenandoah 与 ZGC 类似,也是一个低暂停时间的垃圾收集器,但它是由 Red Hat 开发并贡献给 OpenJDK 项目的。
- JDK 版本:JDK12 引入,JDK15 GA。
- 特点:同样专注于降低 GC 停顿时间,具有更广泛的平台支持。
三、总结
选择合适的垃圾回收器对于提升应用性能至关重要。不同的应用场景可能需要不同类型的垃圾回收策略。例如,对于响应时间敏感的服务端应用,可能更适合使用 G1 或 ZGC;而对于后台批处理任务,则可能更倾向于使用 Parallel 收集器来最大化吞吐量。
随着 JDK 版本的不断演进,新的垃圾回收技术也在持续发展。开发者应密切关注这些变化,并根据实际需求调整自己的垃圾回收策略,以获得最佳的应用性能。希望这篇文章能帮助你更好地理解 JVM 中的垃圾回收机制及其背后的原理。如果你有任何疑问或者想要分享的经验,请在评论区留言!