深入理解Java虚拟机 -- 读书笔记(3):几种垃圾收集器
本系列为《深入理解Java虚拟机 》(周志明著)读书笔记。
垃圾收集器
JVM规范对于垃圾收集器的实现没有任何规定,因此不同厂商、版本的虚拟机所提供的垃圾收集器可能会有很大的差异。这里我们的讨论将基于Sun Hotspot虚拟机1.6版Update22,此虚拟机包含的垃圾回收器如下图:
上图中共有HotSpot 1.6中共有7种垃圾回收器,如果两个垃圾回收器之间有连线,说明二者可以搭配使用。下面我将对这些垃圾收集器一一进行介绍。
Serial收集器
Serial是历史最久的垃圾收集器之一,是一个单线程的收集器。它在进行垃圾收集时,必须暂停其他工作线程,直到它收集结束。Seiral收集器的垃圾回收过程是由虚拟机在后台发起和完成的,在用户不可见的情况下暂停所有工作线程,这将带来恶劣的用户体验,其工作原理如下图所示:
![](http://img-my.csdn.net/uploads/201303/03/1362316451_2463.jpg)
Serial/Serial Old收集器运行示意图
Serial收集器尽管显得“简陋”并且用户体验不佳,但到1.6为止它仍然是虚拟机运行在Client模式下的默认新生代收集器。这主要得益于它的简单高效,并且在桌面应用中,分配给JVM管理的内存一般不会很大,收集几十兆到一两百兆的新生代,停顿时间完全可以控制住几十毫秒最多一百多毫秒内,只要不是频繁发生,这点儿卡顿是完全可以接受的。
ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本,除使用多线程收集职位,其余的行为包括收集算法、Stop the world、对象分配规则、回收策略等都与Serial收集器完全一样,实际上这两种收集器共用了很多代码,ParNew收集器的工作原理如下图所示:
![](http://img-my.csdn.net/uploads/201303/03/1362318745_6261.jpg)
ParNew/Serial Old收集器运行示意图
ParNew是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中一个重要原因是,除Serial收集器外,只有它能与CMS收集器配合工作。CMS是HotSpot虚拟机中第一款真正意义上的并发收集器,我们将在下面详细讨论CMS收集器。
垃圾收集器中的并发与并行
并行(Parallel):指多头垃圾收集器线程并行工作,但此时用户线程仍处于等待状态。
并发(Concurrent):指用户线程与回收器线程同时工作。
Parallel Scavenge收集器
Parallel Scavenge与ParNew很类似,是一个使用复制算法的新生代的并行多线程收集器。那么它与ParNew的区别在哪里?他们之间最大的区别是关注点不一样,ParNew等收集器的关注点在于尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput)。
吞吐量指用于运行用户代码的CPU时间与总CPU时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)。
Parallel Scavenge收集器的另一个特点是可以使用自适应的调节策略。使用这种策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整新生代大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数。其工作过程如下图所示:
![](http://img-my.csdn.net/uploads/201303/03/1362321459_6419.jpg)
Serial Old收集器
Serial Old是Serial收集器的老年代办不,其工作过程如下图所示:
![](http://img-my.csdn.net/uploads/201303/03/1362316451_2463.jpg)
Serial/Serial Old收集器运行示意图
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记 - 整理”算法,其工作过程如下图所示:
![](http://img-my.csdn.net/uploads/201303/03/1362321459_6419.jpg)
CMS收集器
CMS(Concurrent Mark Sweep)收集器是基于“标记 - 清除”算法的并发收集器,其设计目标为获取最短回收停顿时间。它的运作过程比上面介绍的收集器都要复杂一些,整个过程分为四个步骤,包括:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记和重新标记仍需暂停所有用户线程,即Stop the World,但初始标记只是标记GC Roots能直接关联的对象,而重新标记则只是为了修正并发标记期间,因用户程序继续运行而产生变动那一部分对象,这个阶段的停顿时间比前面介绍的Stop the World的时间要短得多。整个收集过程中耗时最久的并发标记和并发清除则和用户线程一起工作,所以总地来讲,CMS中GC线程是和用户线程一起并发执行的。下图可以比较清楚地解释这个过程:
![](http://img-my.csdn.net/uploads/201303/03/1362322804_6043.jpg)
CMS收集器运行示意图
CMS是一款突破性的收集器,它极大地缩短了用户线程停顿时间,可以认为其实现了并发垃圾回收,但金无足赤,人无完人,CMS还是具有这几个缺陷:
- 对CPU资源非常敏感。几乎所有的并行/并发系统都对CPU敏感。虽然它很少导致用户卡顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量变低。
- 无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在同时执行,因此此时这些线程产生的这部分垃圾CMS无法处理,只好留在下一次GC时再清理,这一部分垃圾就被称为“浮动垃圾”。因为在垃圾收集阶段用户线程还在运行,因此CMS需要预留足够的空间供这些线程使用,而不能向其他收集器那样等老年代几乎被完全充满时再进行回收。默认CMS收集器在老年代使用68%之后就被激活。
- 这个缺点来自于CMS所采用的“标记 - 清除”算法。这种方式容易产生大量碎片,当碎片过多时,容易出现老年代空间有很大剩余,但找不到连续空间进行分配给大对象,从而不得不提前触发一次GC。
G1 收集器
G1(Garbage First)收集器是当前收集器技术发展的最前沿成果,它与CMS相比会有两个显著改进:
- 采用“标记 - 整理”算法,避免产生碎片
- 可以精确地控制卡顿。这是通过让使用指定一个参数来控制在一个长度为M的时间片内垃圾回收的时间N。
G1之所以可以在基本不牺牲吞吐量的前提下完成垃圾回收,是因为它能够尽量避免全区域的垃圾回收。之前的收集器是进行收集的范围是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域,并且跟踪这些区域的堆积成都,在后台维护一个优先列表,每次根据优先级从列表中挑选区域进行收集。