HotSpot虚拟机垃圾收集器

534 阅读6分钟

**备注:**基于JDK1.7 Update 14之后的版本。

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器,在JDK1.3.1之前是虚拟机新生代收集的唯一选择。Serial收集器是一个单线程的收集器,需要注意的是这里的“单线程”并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,而是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。即Stop The World。收集器运行过程如下: 

特点: 简单高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集,自然可以获得最高的单线程收集效率。适用于运行在Client模式下的虚拟机

ParNew收集器

ParNew收集器时Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余的行为都与Serial收集器完全一样。ParNew收集器的示意图如下: 

ParNew收集器适用于运行在Server模式下的虚拟机的新生代收集器。并且它能够与CMS收集器配合工作。

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,使用的时复制算法,是并行的多线程收集器。Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。吞吐量指的是CPU用于运行用户代码的时间与CPU总耗时时间的比值,即:

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

比如,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。 停顿时间越短就越适合需要与用户进行交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效地利用CPU时间,尽快完成程序的运算任务,主要适用于后台运算而不需要太多交互的任务。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,使用“标记—整理”算法。示意图如下:

该收集器也是给Client模式下的虚拟机使用。如果是在Server模式下,还可以有两个用途:

  • 在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用;

  • 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记—整理”算法。示意图如下: 

CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。是基于“标记—清除”算法实现的。它的运作过程分为4个步骤:

  1. 初始标记 初始标记需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。

  2. 并发标记 并发标记阶段就是进行GC Roots Tracing的过程。

  3. 重新标记 重新标记阶段需要“Stop The World”。该阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这一阶段的停顿时间一般会比初始标记阶段稍长一些,但比并发标记的时间短。

  4. 并发清除 该阶段是对被标记的对象进行清除,回收内存。

示意图如下: 

**优点:**并发收集,低停顿。

缺点:

  1. CMS收集器对CPU资源非常敏感

  2. CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在JDK1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会激活,如果在应用中老年代增长不是太快,可以适当调高参数**-XX:CMSInitiatingOccupancyFraction**的值来提高触发百分比,以便降低内存回收次数从而获得更好的性能。在JDK1.6中,CMS收集器的启动阈值已经提升至92%。

  3. CMS是基于“标记—清理”算法实现的,收集结束时可能会有大量碎片空间产生。空间碎片过多时,将会给大对象的分配造成很大的麻烦,往往会出现老年代还有很大的剩余空间,但是无法分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个开关参数:-XX:+UseCMSCompactAtFullCollection,该参数默认是开启的,用于在CMS顶不住要进行Full GC是开启内存碎片的合并整理过程。

G1收集器

G1(Garbage-First)收集器是一款面向服务端应用的垃圾收集器。具备如下特点:

  • 并行与并发

  • 分代收集

  • 空间整理

  • 可预测的停顿

G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分不需要连续的Region的集合。 在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描。G1中每一个Region都有一个与之对应的Remembered Set,虚拟机发现程序在Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set即可保证不对全堆扫描也不会有遗漏。

G1收集器的运作大致可划分为以下几个步骤:

  1. 初始标记 初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运作时,能在正确可用的Region中创建新对象,这一阶段需要停顿线程,但耗时很短。

  2. 并发标记 并发标记阶段是从GC Root开始对堆中的对象进行可达性分析,找出存活的对象,这一阶段耗时较长,但是可以和用户线程并发执行。

  3. 最终标记 最终标记是为了修正在并发标记期间因用户程序继续执行而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这一阶段需要停顿线程,但是可以并行执行。

  4. 筛选回收 筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC时间来指定回收计划。

G1收集器的运行示意图如下: