垃圾收集器

垃圾收集算法是内存回收的方法论,垃圾收集器是垃圾回收的具体实现。在JDK1.7 Update 14之后的HotSpot虚拟机所包含的收集器如下图所示:

垃圾收集器

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器。这是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Serial收集器运行示意图如下:

垃圾收集器

也就是说,虚拟机在使用Serial垃圾收集器时会在用户不可见的情况下把所有用户正常工作的线程暂停,这种“Stop The World”操作在很多程序中是难以接受的,带给用户不良体验。
举个例子“你妈妈在打扫房间的时候,肯定也会让你老老实实待在椅子上或者房间外待着,如果她一边打扫,你一边丢垃圾,这房间还能打扫完?你妈妈不会崩溃?”,因此看起来“Stop The World”操作情有可原。
但是,从JDK1.3开始,HotSpot虚拟机开发团队一直在努力消除或减少工作线程因内存回收而导致的停顿,从Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)乃至GC收集器最前沿的成果Garbage First(G1)收集器,用户线程的停顿时间在不断缩短,但是仍然没有办法完全消除垃圾回收带来的停顿。
Serial收集器虽然很老,可以说很陈旧,但是目前为止它依旧是虚拟机运行在Client模式下的默认新生代收集器。那是因为Serial收集器简单而高效,对于限定单个GPU的情况而言,Serial收集器由于没有线程交互的开销,专心进行垃圾收集自然可以获得最高的单线程收集效率。

ParNew收集器

ParNew收集器就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为包括Serial收集器的所有可用控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样。ParNew收集器进行垃圾收集的示意图如下:

垃圾收集器

ParNew收集器除了多线程收集外,其他与Serial收集器相比没有太多创新之处,但它却是虚拟机运行在Server模式下的首选新生代收集器,其中一个与性能无关的重要原因是除了Serial收集器外,目前只有它能与CMS收集器配合工作。

Parallel Scavenge收集器

Parallel Scavenge收集器也是新生代收集器,并且使用复制算法收集内存,同时还是并行的多线程收集器。这些特点都和ParNew收集器一样,但是Parallel Scavenge收集器的目标在于达到一个可控制的吞吐量(Throughput),所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码/(运行用户代码+垃圾收集时间)。这与CMS等收集器的目标不同,它们目标是尽可能地缩短垃圾收集时间用户线程的停顿时间。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多的交互任务。
Parallel Scavenge收集器提供两个参数用于精准控制吞吐量,分别是控制最大垃圾收集时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
MaxGCPauseMillis参数允许的值是一个大于0的毫秒数,收集器将尽可能在规定时间内完成垃圾收集操作。但是不要认为这个参数越小,系统的垃圾收集速度就会越快,GC停顿时间的缩短是以牺牲吞吐量和新生代空间来换取的:系统把新生代调小一点,收集300M新生代肯定比收集500M新生代快,这也将导致垃圾收集更频繁,原来每20s收集一次、一次100ms,现在每10s收集一次、一次70ms。停顿时间的确下降了,但吞吐量也降下来了。
GCTimeRatio参数的值应当是一个大于0小于100的整数,如果把这个参数设置为19,那么允许最大GC时间就占总时间的5%(即1/(1+19))。
Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy,这个参数打开之后就不需要手动指定新生代的大小(-Xmm)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或者最大吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。

Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
在Server模式下,它有两大用途:

  • 在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用;
  • 作为CMS收集器的后备方案,在并发收集发生Concurrent Mode Failure时使用。

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。在JDK1.6之前Parallel Scavenge只能与Serial Old收集器配合使用,由于老年代使用单线程收集内存无法充分利用服务器多CPU的处理能力,这个组合就导致无法达到高吞吐量。在JDK1.6开始提供了Parallel Old收集器和Parallel Scavenge收集器配合使用,从而可以应用在注重吞吐量以及CPU资源敏感的场合。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。从名字(包含“Mark Sweep”)就可以看出来,CMS收集器是基于“标记——清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)
  • 并发清除(CMS concurrent sweep)

其中,初始标记、重新标记这;两个步骤仍需要“Stop The World”。初始标记是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段是为了修正并发标记阶段因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这一阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记阶段的时间短。整个过程中耗时最长的并发标记和并发清除阶段都是与用户线程一起工作,如下图所示:

垃圾收集器

CMS是一款优秀的收集器,它的主要优点是并发收集、低停顿,但是它还有以下三个明显缺点:

  • CMS收集器对CPU资源非常敏感;
  • CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次的Full GC。
  • CMS收集器是基于“标记-清除”算法实现的,在收集结束会产生大量的空间碎片。为了解决这个问题,CMS收集器提供了一个-XX:+USeCMSCompactAtFullCollection开关参数用于在CMS收集器顶不住要进行Full GC时开启内存碎片的合并整理过程。

G1收集器

G1收集器是当前收集技术最前沿的成果之一,它被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。G1是一款面向服务端应用的垃圾收集器,与其他收集器相比,G1具备如下特点:

  • 并行与并发
  • 分代收集
  • 空间整合
  • 可预测的停顿

在G1之前的其他收集器进行收集的范围是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大区别,它将整个Java堆划分成多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

-------------本文结束感谢您的阅读-------------