JVM进阶之GC(四)垃圾收集器

上一篇讲了下垃圾回收算法,今天就来说说垃圾回收算法的具体实现吧–垃圾收集器(本文讨论的是JDK1.7版本的HotSpot虚拟机)。

垃圾收集器

HotSpot虚拟机提供的收集器如下图:
垃圾收集器
新生代的垃圾收集器有:Serial、ParNew、Parallel Scavenge;
老年代的垃圾收集器有:CMS、Serial Old、Parallel Old
G1收集器因运用的还不是很广泛,不予讨论,有兴趣的可自行百度。
收集器之间有连线,表示他们可以搭配使用。为什么需要搭配使用呢?而且收集器还这么多?应该说有什么样的需求就有什么样的产品,垃圾收集器也是按需设计的,没有最好的产品,只有最合适的。那么各个收集器的实现原理是怎样的,有什么特点呢?下面一一来讨论。

Serial与Serial Old 收集器

Serial,翻译成中文的意思是“串行”,顾名思义,这就是个单线程的收集器。仅仅使用一个线程去执行垃圾收集任务,而且收集任务期间,必须停掉其他的工作线程,直到垃圾收集完成。垃圾回收时停掉其他的线程的现象,就称为“Stop The World(STW)”。打个比方,我清扫房间的时候,任何人都不能在家里活动,以免给我捣乱,不然清扫工作怎么也没法做完。STW就是这么个意思,至于暂停应用多久,得看具体垃圾的情况了。
Serial收集器是收集新生代的收集器,而Serial Old收集器是收集老年代的,上图也看到了它们之间有连线可搭配使用,看如下它们搭配使用的运行图:
Serial与Serial Old搭配使用
①:新生代使用Serial收集器,采用复制算法,会暂停其他用户线程(STW)专心做垃圾回收。
②:老年代使用Serial Old收集器,采用标记整理算法,会发生STW。

ParNew 收集器

ParNew其实就是Serial的多线程版本,在新生代中使用多条线程进行垃圾回收。看如下逻辑图就一目了然了:
ParNew收集器
①:新生代使用ParNew收集器,可以看到有多条GC线程在进行垃圾回收,采用复制算法,会暂停其他用户线程(STW)专心做垃圾回收。
②:老年代使用Serial Old收集器,采用标记整理算法,会发生STW。

Parallel Scavenge 收集器

Parallel Scavenge 收集器也是新生代收集器,也是使用复制算法的多线程收集器。
看上去和ParNew收集器差不多,但是Parallel Scavenge最大的特点是更关注吞吐量
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值:

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

打个比方,虚拟机运行了100分钟,垃圾回收用了2分钟,那么吞吐量就是98%。
按照公式来看,吞吐量越高的虚拟机,自然是垃圾收集时间也越短,理所当然的用户体验也要更好。Parallel Scavenge收集器会根据当前系统的运行情况,动态调整某些参数来提供最合适的停顿时间或最大的吞吐量,这就是GC的自适应调节策略,这也是其与ParNew收集器最明显的区别。

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge收集器的老年代版本,运用多线程和标记整理算法收集。从最上面的搭配图也可以看到,Parallel Old 只能与Parallel Scavenge配对使用。这样的组合,在注重吞吐量和CPU资源的场合使用比较合适。如下是逻辑运行图:
Parallel Old收集器
①:新生代使用Parallel Scavenge收集器,可以看到有多条GC线程在进行垃圾回收,采用复制算法,会暂停其他用户线程(STW)专心做垃圾回收。
②:老年代使用Parallel Old收集器,使用多线程采用标记整理算法,会发生STW。

CMS 收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。在B/S架构模型的网站上,运用CMS收集器十分广泛,因为网站上更希望停顿越短越好,用户体验才能更好。
CMS收集器是基于标记清除算法实现的,但是其运行过程相对来说更复杂了,整个过程分成下图4个步骤:
CMS收集过程

  • ①:初始标记(initial mark)
    在图中可以看出这个步骤是单线程处理的,并且用户线程并未运行,是因为出现了STW。这个过程只是标记一下GC Roots能直接关联到的对象,速度很快。
  • ②:并发标记(concurrent mark)
    这个阶段就是进行GC Roots Tracing过程,可以看出GC线程与用户线程并发工作,所以并发标记过程并不影响用户线程的使用。
  • ③:重新标记(remark)
    重新标记阶段是为了修正并发标记期间,因用户线程继续运行导致标记产生变动的那一部分对象的标记。看起来有点绕,其实意思就是在并发标记时,用户线程也会产生需要标记的对象,这部分对象不能漏了标记,所以就需要重新标记过程。在图中可以看到,没有用户线程在运行,说明需要STW。
  • ④:并发清除(concurrent sweep)
    并发清除这个阶段看图也能类比了,有GC线程与用户线程并发运行,GC线程清理掉那些标记的对象,用户线程正常运行。

整体来看,CMS收集器的垃圾回收过程是与用户线程一起并发执行的。
但是CMS收集器还是有一下三个缺点:

  1. 因为是使用并发收集,虽然不会导致用户线程停顿,但是会占用一部分线程而导致应用程序变慢,总的吞吐量会降低。
  2. CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的发生。因为在并发清理阶段,用户线程还在运行,自然就还有新的垃圾不断产生,这部分垃圾出现在标记过程之后,CMS也束手无策,只能等待下次GC时再清理,这一部分垃圾就叫“浮动垃圾”。
  3. CMS是基于标记清除算法实现的,前面的文章也提到过标记清除算法的缺点,就是会产生大量的空间碎片。空间碎片过多时,就会给大对象的空间分配带来麻烦。比如老年代有足够的空间,但是找不到连续的足够大的空间,而不得不触发一次Full GC。为了解决这个问题,CMS收集器提供了-XX:+UseCMSFullGCsBeforeCompaction参数,用于设置执行了多少次不压缩的FGC后来一次碎片整理(默认是0,每次进入FGC时都进行碎片整理)。

虽然CMS有几个缺点,但是进行合理的参数配置,在老年代的垃圾回收上还是有不俗的表现。目前的主流搭配使用是ParNew+CMS收集(譬如我们公司。。)。

好了,今天就到这里,下期将谈谈对象的内存分配和回收策略。

你可能感兴趣的:(jvm,JVM进阶)