Java中常用的垃圾回收器
在Java的世界里,"垃圾回收"是一个让许多开发者即侍俯首也感到神秘的术语。垃圾回收(Garbage Collection, GC)是Java虚拟机(JVM)的一个重要部分,它帮助开发者管理内存,确保程序能有效并且安全地运行。本文将介绍Java中几种常用的垃圾回收器,以及它们的工作原理和适用场景。
1. 什么是垃圾回收?
在Java中,对象是在堆内存上分配的,当这些对象不再被任何部分的应用所引用时,这些对象就被认为是垃圾。垃圾回收器的任务就是找到这些不再被需要的对象,并释放它们占据的内存空间,以供新对象使用。
2. 为什么需要垃圾回收?
无法控制内存是许多程序可能遇到的问题。例如,内存泄漏,这通常发生在程序不再需要的数据没有被及时清理的情况下,长此以往可能导致内存耗尽,最终程序崩溃。通过自动垃圾回收,Java试图避免这种情况。
3. 垃圾回收器的种类
3.1 Serial GC
Serial GC是最基本的垃圾回收器,适用于单核处理器的环境。它在进行垃圾回收时会暂停所有的应用线程("Stop-The-World"事件),这通常不适用于多线程的服务器环境,但可能适合于简单的命令行程序。
使用参数:
-XX:+UseSerialGC
3.2 Parallel GC
Parallel GC又称为Throughput Collector,它使用多个线程来执行垃圾回收,因此它比Serial GC更适合多核心服务器环境。其主要关注点是提高吞吐量,缺点是在垃圾回收时仍然会有应用线程暂停。
使用参数:
-XX:+UseParallelGC
3.3 CMS GC
CMS GC(Concurrent Mark Sweep )的目标是减少因垃圾回收导致的停顿时间。它在回收内存的同时允许应用程序线程继续执行,适合于交互性强的应用,其中快速响应比吞吐量更重要。
使用参数:
-XX:+UseConcMarkSweepGC
3.4 G1 GC
G1 GC (Garbage-First)是一种服务器端垃圾回收器,它旨在填补Parallel GC和CMS GC之间的空白,提供一个低延迟的垃圾回收解决方案
。G1将堆内存分成多个不同的区域,并根据每个区域可能包含垃圾的大小来确定回收的优先顺序。
使用参数:
-XX:+UseG1GC
3.5 Z GC
ZGC是一种低延迟的垃圾回收器,目前仍在积极开发中。ZGC的设计初衷是为了在大堆内存上工作,并且几乎不产生延迟。这使得它非常适合需要快速响应但是内存占用大的应用程序。
使用参数:
-XX:+UseZGC
3.6 Shenandoah GC
Shenandoah GC与ZGC有类似的目标:减少停顿时间,即便是在大堆或者多核心处理器的情况下。Shenandoah通过在GC的许多阶段与应用线程并发执行来实现这一目标。
使用参数:
-XX:+UseShenandoahGC
4. 怎样选择合适的垃圾回收器?
在选择垃圾回收器时,需要考虑应用程序的需求:
- 对于需要最大吞吐量的应用程序,Parallel GC可能是最好的选择。
- 如果应用需要较短的响应时间或者较少的停顿时间,可以考虑CMS或G1。
- 对于需要极低停顿时间并且具有大内存容量的应用,ZGC或者Shenandoah可能是合适的选择。
- 在实际中,最好的方法是通过实际的性能测试来确定最适合你的应用的垃圾回收器。
5. JDK 8 默认垃圾回收器 Parallel
JDK 8(Java Development Kit 8)中默认的垃圾回收器组合为Parallel Scavenge(用于Young Generation)加上Parallel Old(用于Old Generation)
。Parallel GC 在 JDK 5 中开始成为默认的垃圾回收器。
Parallel Scavenge收集器是一个新生代垃圾收集器,它使用复制算法
,而且是并行的多线程收集器
。它主要关注于达到一个可控制的吞吐量(Throughput)。所谓吞吐量,就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即:吞吐量 = 用户代码时间 /(用户代码时间 + 垃圾收集时间)。服务器环境中,这种收集器能够最大化的利用CPU时间,尽快地完成程序的运算任务,特别适用于多核服务器。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本
,使用多线程和"标记-整理"
算法。这个收集器是在JDK 6u14版之后引入的。从JDK 9 开始默认垃圾回收器是 G1 (Garbage-First)
6.为何CMS不能作为默认垃圾回收器
CMS(Concurrent Mark-Sweep)收集器是一个以获取最短回收停顿时间为目标的收集器
,使用"标记-清除
"算法,并且是并发的。但是在JDK版本的迭代中没有任何一个版本使用CMS为一个默认的垃圾回收器。
选择Parallel Scavenge和Parallel Old的原因而不用CMS,通常基于以下考虑:
-
吞吐量优先: 如果应用不是非常注重服务响应时间,而是更希望在单位时间内完成更多的工作,即追求较高的吞吐量,那么Parallel Scavenge加上Parallel Old垃圾收集器会是一个更合适的选择。
-
碎片化问题:CMS使用的"标记-清除"算法容易导致内存碎片化,当碎片化严重到一定程度,CMS需要进行一次完全停顿的垃圾收集以整理内存碎片(Full GC),这可能导致较长时间的停顿。而Parallel Old使用的"标记-整理"算法可以有效避免内存碎片化问题。
-
更简单稳定: 相较于CMS,Parallel Scavenge和Parallel Old通常来说维护起来比较简单,参数配置也更容易。而CMS有几百个参数需要配置调整,对于维护的成本是非常高的。
随着Java 9的发布,G1(Garbage-First)垃圾收集器成为了默认的垃圾收集器,它旨在替代CMS,提供更好的性能表现和更简单的调优选项。而在Java 11之后,还引入了ZGC和Shenandoah等实验性的低停顿时间垃圾收集器。
7. CMS和G1比较
CMS(Concurrent Mark Sweep)和G1(Garbage-First)是Java虚拟机中的两种不同的垃圾回收器,它们各自有不同的特点和适用场景。
7. 1 CMS垃圾回收流程
- 初始标记(Initial Mark):标记所有与GC Roots直接相连的对象,这个阶段需要停顿线程,但是时间很短。
- 并发标记(Concurrent Mark):在整个堆中进行可达性分析,查找所有可达对象,这个阶段和应用线程并发执行,不需要停顿。
- 预清理(Preclean):进行一些预清理工作,减少最终标记时的工作量。
- 最终标记(Remark):完成剩余的标记工作,为了减少停顿时间,通常使用多种优化算法,比如增量更新、卡表等,这个阶段需要短暂停顿。
- 并发清除(Concurrent Sweep):清除不再使用的对象,回收空间,这个阶段和应用线程并发执行。
- 并发重置(Concurrent Reset):重置CMS算法相关的内部数据结构,为下一次GC循环做准备。
7. 2 G1垃圾回收流程
- 初始标记(Initial Mark):这个阶段与CMS的初始标记类似,标记所有从GC Roots直接可达的对象,需要短暂停顿。
- 根区域扫描(Root Region Scanning):扫描由Survivor区域到Old区域的引用。
- 并发标记(Concurrent Mark):进行标记阶段,计算各个区域的存活对象,这个过程是和应用程序并发的。
- 最终标记(Remark):完成标记过程,通常使用SATB(Snapshot-At-The-Beginning)算法来减少标记过程的时间。
- 筛选回收(Evacuation):根据之前的标记结果,选择价值最大的若干个Region进行回收。这个阶段需要停顿,但是只清理有限的区域。
G1收集器是为了替代CMS收集器而设计的,提供更可预测的垃圾回收暂停时间,特别是在处理大内存容量的多处理器机器时。G1能够更好地控制停顿时间,通过将堆分割成多个区域(Region)来避免整个堆的垃圾回收,逐步地清理内存,特别适合大堆内存的应用。
7. 3 两者比较
- 停顿时间控制:G1具有更好的停顿时间控制能力,可以为应用程序设置停顿时间目标。
- 内存占用:G1通常比CMS需要更多的内存空间,因为它需要维护更多的内存结构。
- 预测性能:G1可以更准确地预测垃圾收集的停顿时间,因为它是基于区域的回收。
- 适用场景:CMS适用于对响应时间比较敏感的应用。而G1适用于堆内存较大的场景,特别是超过4GB的堆内存。
随着Java虚拟机的发展,G1由于其更好的可预测性和适应性,越来越多地成为默认的垃圾收集器,尤其是在Java 9及以后的版本中。