JVM垃圾收集器总结

  我们在《JVM中的垃圾收集算法》中介绍了垃圾收集算法,如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,所以不同的厂商、不同版本的虚拟机可以根据需要提供不同的收集器。我们这里讨论的收集器是基于JDK1.7 Update 14之后的HotSpot虚拟机,其包含的所有收集器如图1所示:

image_1b0ig2cvncat1l5s96ahaj11e89.png-71.7kB

          图1 HotSpot虚拟机的垃圾收集器
          
  图中上下两个区域分别代表收集器是属于新生代收集器还是老年代收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。
  
  另外,在开始下面的介绍之前,我们有必要先解释两个名词:并发和并行。这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,他们可以解释为:
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),
用户程序继续运行,而垃圾收集程序运行于另一个CPU上。

Serial(串行GC)收集器:

  Serial收集器是一个新生代收集器,单线程执行,使用复制算法。其中“单线程”的意义不仅仅说明它只会使用一个CPU或者一条收集线程区完成垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束,即“Stop The World”,这好比你妈妈在给你打扫房间的时候,你必须老老实实地坐在椅子上,而不能她一边打扫,你还一边乱扔纸屑。Serial收集器是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。其工作过程如图2所示:

image_1b0ih2iq21lf51mk8fhvbfi8713.png-53.5kB

          图2 Serial/Serial Old收集器运行示意图

ParNew(并行GC)收集器:

  ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。其工作过程如图3所示:
  
  image_1b0ih6vgtfsq1shuokhgnjkc81g.png-60.3kB

          图3 ParNew/Serial Old收集器运行示意图           

Parallel Scavenge(并行GC)收集器:

  Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。
  parallel Scavenge收集器看上去与ParNew一样,其特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

Serial Old(串行GC)收集器:

  Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法,其工作过程如上面图2所示。

Parallel Old(并行GC)收集器:

  Parallel Old是Parallel
Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

CMS(并发GC)收集器:

  CMS(Concurrent Mark Sweep)收集器是基于“标记 - 清除”算法的并发收集器,其设计目标为获取最短回收停顿时间。它的运作过程比上面介绍的收集器都要复杂一些,整个过程分为四个步骤,包括:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

  初始标记和重新标记仍需暂停所有用户线程,即“Stop the World”。初始标记只是标记GC Roots能直接关联的对象,速度很快。并发标记阶段,用户线程与标记线程并发,就是进行GC Roots Tracing的过程。而重新标记则只是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。整个收集过程中耗时最久的并发标记和并发清除则和用户线程一起工作,所以总地来讲,CMS中GC线程是和用户线程一起并发执行的。其工作过程如图4所示:
  
  image_1b0ii8hdh1qlp1ob81kdg1fgh12nu2n.png-98.7kB
  
          图4:CMS收集器运行示意图

  CMS是一款突破性的收集器,它采用并发收集,极大地缩短了用户线程停顿时间。但同时,CMS还是具有一些缺陷:
1、对CPU资源非常敏感。几乎所有的并行/并发系统都对CPU敏感。虽然它很少导致用户卡顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量变低。
2、无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在同时执行,因此此时这些线程产生的这部分垃圾CMS无法处理,只好留在下一次GC时再清理,这一部分垃圾 就被称为“浮动垃圾”。因为在垃圾收集阶段用户线程还在运行,因此CMS需要预留足够的空间供这些线程使用,而不能向其他收集器那样等老年代几乎被完全充 满时再进行回收。默认CMS收集器在老年代使用68%之后就被激活。
3、这个缺点来自于CMS所采用的“标记 - 清除”算法。这种方式容易产生大量碎片,当碎片过多时,容易出现老年代空间有很大剩余,但找不到连续空间进行分配给大对象,从而不得不提前触发一次GC。

G1 收集器:

  G1(Garbage First)收集器是当前收集器技术发展的最前沿成果,它与其他GC收集器相比,具有如下特点:
1、并行+并发。可充分利用CPU资源;
2、分代收集;
3、G1从整体看是“标记-整理”算法,从局部(两个Region之间)看,是“复制”算法。 不会产生空间碎片;
4、可预测的停顿。建立可预测的态度时间模型,能让使用者明确指定在一个长度为M毫秒的时间内,消耗在垃圾收集的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。

  G1之前的收集器是进行收集的范围是整个新生代或 老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域(Region)。G1跟踪这些Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据优先级从列表中挑选回收价值最大的Region进行回收(这也就是Garbage First名称的由来)。其工作过程类似于CMS,分为如下四个步骤:   

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

  前三个步骤与CMS类似,最后的筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。