HotSpot虚拟机的垃圾收集器

垃圾收集器就是使用一种或者多种垃圾回收算法,在不一样的内存分配策略下,进行垃圾回收的程序。因为内存分配策略不一样的缘由,因此垃圾回收算法以及垃圾收集器的种类也不尽相同,下图是JDK1.7 Update 14以后的HotSpot虚拟机所包含的全部的垃圾收集器。java

                    

HotSpot中的收集器分类算法

 

图上整体分为两大类,年轻代(Young generation)和老年代(Tenured generation)。上图中的连线表示在年轻代和老年代之间,哪些收集器能够两两配合使用,“八字不合”的收集器是不能在一块儿的!注意到,G1收集器是当前最前沿的收集器,它不须要和其余收集器配合,本身就能完成年轻代和老年代的垃圾回收工做。多线程

 

1.Serial收集器并发

Serial收集器应该算是收集器家族中元老级的任务,它是一款历史比较悠久的收集器,在JDK1.3以前仍是新生代收集器的惟一选择。Serial的中文意思是“串行”,也就是说他是一个单线程工做的垃圾收集器,其全部的工做都是在单个线程单个CPU的状况下完成的。除了单线程以外,Serial收集器在进行垃圾回收的过程当中,须要暂停其余全部的用户线程,JAVA专家称这种状况为:“Stop The World”。这种好比真的是再形象不过了,由于在那一刻真的好像世界都中止了。有人举过一个形象的例子,你女友在给你打扫放假的时候,确定不会让你再处处乱跑,若是她一边打扫,你一边乱跑,极可能晚上就要睡沙发了。实际上,“Stop The world”致使的停顿现象,即使是如今的最新的G1垃圾收集器也不可能说彻底避免,而只能不断地缩短Stop The World的时间。布局

经过上面的介绍,你们可能以为Serial收集器多是一个又老又慢过期的收集器,估计已经被淘汰了。然而事实上并不是如此,它到目前为止依赖是虚拟机运行在client模式下的默认年轻代收集器。咱们先来看一下Serial收集器的工做的过程,以后能够和后面的几个收集器在进行对比,就能发现Serial收集器在某些特殊的环境下,仍是有优势的:性能

        

Serial/Serial Old收集器回收过程优化

 

正式由于Serial的单线程,因此它相比于其余的收集器来讲,优势就是简单而高效,对于单个CPU的环境的来讲,Serial收集器没有线程交互,因此回收的效率相对来讲就比较高。因此Serial收集器更适合运行在client模式下的虚拟机,如桌面应用等。ui

 

关于JVM Client模式和Server模式:Client模式采用的是轻量级的虚拟机,全部启动的比较快;Server模式采用的是重量级的虚拟机,因此启动比较慢。重量级的虚拟机在运行期间作了不少的优化,因此启动以后程序运行比较快。Client模式通常内存占用的状况比较小,VM在client模式默认-Xms是1M,-Xmx是64M;JVM在Server模式默认-Xms是128M,-Xmx是1024M;因此Client在进行Stop The World的时间相对来讲不会太长,因此使用简单快速的Serial收集器来进行垃圾回收时再合适不过的了。线程

 

2.ParNew收集器设计

ParNew名字前面的Paral其实是单词“parallel”的缩写,意思是并行。因此从名字就能够知道ParNew收集器应该是多线程,事实上它就是Serial收集器的多线程的版本,因此,它除了是多线程的以外,其余的全部的东西像控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器彻底同样。ParalNew收集器回收过程以下图所示:

            

ParNew / Seria Old收集器回收过程

 

ParNew收集器相对于Serial收集器除了多线程以外,并无其余的特色。因此若是在单CPU的状况下,ParalNew收集器并不会比Serial的性能好,甚至由于存在线程的交互的开销,ParalNew性能反而更差。另外就是,在JDK1.5时期推出了CMS(Concurrent Mark Sweep),这个几乎被认为有划时代意义的垃圾收集真正实现了垃圾回收的并发操做,也就是用户线程和GC线程是同时工做的。可是你们从开头的分类图上能够看出,在新生代中,能够和CMS同时工做的只有Serial和ParNew能够与之搭配工做。因此,在多核的环境下,ParNew收集器是最佳的选择。

 

3.Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,它也是使用了复制算法的收集器(前面介绍的两种收集器都是复制算法),并且又是并行的多线程收集器,看起来好像和ParNew收集器没什么不一样。哲学上常说,存在便是合理的,一件东西不会无缘无故的出现,Parallel Scavenge也是同样,那么它到底有什么神奇之处呢?

Parallel Scavenge收集器的特色是它的关注点和其余的收集器不同,CMS收集器的关注点是尽量是缩短垃圾回收时用户线程的停顿时间,而Parallel收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值:

 

          

 

停顿时间越短越适合与用户交互的程序,由于与用户交互的程序须要有良好的响应速度来提高用户的体验。也就是说,在一系列的用户操做中,用户并不但愿出现长久停顿的状况。就比如你正在看某种大片,过程当中每隔一段时间就停顿一下,你确定很不爽。假如这个停顿的时间就是垃圾回收致使的,因此你固然但愿这个停顿的时间越短越好。Parallel Scavenge就是用来将吞吐量控制在一个能够接受的范围内。Parallel Scavenge提供几个参数来精确控制吞吐量:

 

参数

参数描述

-XX:MaxGCPauseMillis

该参数容许设置一个大于0的毫秒数,收集器将尽量地保证

内存花费的时间不超过该值

-XX:GCTimeRatio

该参数容许设置一个0~100之间的整数,也就是垃圾收集时间占用比,至关于吞吐量的倒数

-XX:+UseAdaptiveSizePolicy

该参数是一个开关参数,打开以后就不须要制定新生代的大小(-Xmm),Eden与Suirvive的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PreTenureSizeThreshold)等细节参数了,虚拟机会根据系统运行状况,动态调整这些参数

 

注意:虽然Paralle Scavenge的参数是能够精确控制程序运行的吞吐量的,可是并非GC停顿时间控制越小越好。由于GC停顿时间变短,是以牺牲GC吞吐量和新生代空间来换取的。也就是说,太小的GC停顿时间会致使更频繁地发生GC。因此,Paralle Scavenge参数要设置在一个合理的范围内,才能让系统更好的运行。若是对于这方面没有经验的人,可使用参数3,让系统本身决定吞吐量。

 

4.Serial Old收集器

Serial Old收集器相至关于Serial收集器的老年代版本,使用的是标记-整理的算法,一样他是一个单线程的收集器。如今这个收集器的主要做用就是在Client模式下,做为虚拟机老年代的回收的垃圾收集器使用。可是若是你要把用在Server模式中,他就具有下面两种用途:

  1. 在JDk1.5以前包括1.5的版本,与Parallel Scvenge 收集器搭配使用;
  2. 做为CMS收集器的后备方案,也就是并发收集发生Conrurrent Mode Failure时做为兜底的方案使用。

 

Serial Old收集器的工做过程以下图所示:

            

ParNew / Seria Old收集器回收过程

 

5.Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用的是多线程和标记-整理。这个算法是在JDK1.6以后才提供的,因此Serial Old才能够做为Parallel Scavenge收集器Jdk1.5以前的年轻代收集器配合使用。因此在JDK1.6出现以前,Parallel Scavenge收集器是比较尴尬的,一方面本身在年轻代中使用的是多线程的,而在老年代中,只有单线程的Serial Old能够选择。因此在Parallel Old收集器出现以前,Parallel Scavenge收集器的地位是十分尴尬的。因为在老年代中Serial Old的效率拖后腿,因此Parallel Scavenge收集器一直处于一种不给力的状态。

直到Parallel Old收集器出现以后,“吞吐量优先”才有了比较名副其实的应用组合,因此JDk1.6以后,在注重吞吐量以及CPU资源敏感的场景中,均可以优先考虑Parallel Scavenge加Parallel Old的组合。他们组合的垃圾回收过程以下图所示:

            

Parallel Scavenge / Parallel Old收集器回收过程

 

5.CMS收集器(Concurrent Mark Sweep)

CMS收集器的目标是以得到最短回收停顿时间为目标的收集器(注意和Parallel Old收集器加以区分,Parallel Old是目标是精确控制程序运行的吞吐量,即停顿时间可控制)。从名称上咱们能够看出来CMS使用的标记-清除的算法,它的运行过程相对来讲比较复杂,整个过程能够分为4个步骤:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 从新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

 

咱们能够先来看一下CMS收集器的垃圾回收过程,以下图所示:

            

CMS垃圾回收过程

 

从图上能够看出,在初始标记和从新标记这两个阶段仍然须要"Stop The World"。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。并发标记的阶段就是进行GC Roots Tracing的过程,而从新标记的过程就是为了修正并发标记期间由于用户程序仍然运行而致使变更的那一部分对象的标记记录。这个过程当中的时间要比初始标记长一些,可是要远比并发标记的时间要短。因此CMS把标记阶段能够大体理解为两个阶段,一个阶段是能够并发标记的阶段,不会影响程序运行;另外一个阶段就是不能够并发标记的阶段,就是标记过程会影响程序运行,因此这个阶段要“Stop The World”。可是由于大部分标记工做已经在第一个阶段完成,因此第二部分的时间就很是短。相比于以前的收集器不加以区分的方式,这种方式能够大大减小由于标记而致使“Stop The World”停顿的时间。

从设计来讲,CMS使用的也是多线程的方式来进行垃圾回收,因为是和用户线程同时进行,且使用的标记-清除算法,因此它就存在如下三个明显的缺点:

 

  1. CMS默认启动的回收线程数是(CPU数量+3)/4,也就是说当CPU数量在4个以上的时候,并发回收时垃圾收集线程数量很多于25%,而且随着CPU数量的增长而降低。可是当CPU数量不足于4个时,CMS对于用户程序影响就比较大,由于极可能有很多于一半的线程用来垃圾回收,等于性能一会儿降了一半,是没法忍受的。因此,使用CMS收集器最好是在4以上的CPU数量的环境下才能有很好的体验。

 

  1. CMS收集器没法处理浮动垃圾,浮动垃圾听着好像是很吊的东西,其实也很好理解。就是由于在并发清理的过程是和用户线程同时并发进行的,因此在清理的过程当中,会产生新的垃圾,这些新的垃圾已经不能当即回收,只能等到下次垃圾回收的时候再清理。等于说,有一段时间这些没有用内存空间被浪费掉。并且因为在垃圾清理的阶段,用户程序在运行,因此老年代中必需要预留一段空间给用户线程使用。这里要涉及到一个参数配置:-XX:CMSInitiationOccupancyFraction,这个参数表示老年代使用了多少百分比的时候激活CMS收集器。若是这个参数设置的比较高,那么CMS垃圾回收的频率就要低,老年代中的浮动垃圾就会滞留的比较大,那么致使“Concurrent Mode Failure”的概率就比较大,由于老年代剩余的空间不足于知足用户线程的运行,因此会触发一次Full GC(也就是前面提到的,会使用CMS后背方案,也就是用Serial Old垃圾收集器来进行一次老年代的垃圾回收),这种状况下停顿的时间就会比较长。频繁的发生这种事情,性能就会下降,因此这个参数设置对于CMS性能相当重要。

 

  1. CMS使用的是标记-清理的算法来进行垃圾回收的,了解标记-清理的算法确定知道,这种算法会致使空间碎片。若是空间碎片过多,那么在进行大对象空间分配的时候极可能不能知足大对象的连续空间的要求,从而致使了一次Full GC。为了解决这个问题,CMS收集器提供了一个参数:-XX:UseCMSCompactAtFullCollection,这是一个开关,用来控制CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程。这个过程虽然解决了内存碎片的问题,可是因为内存碎片整理的过程当中是没法并发,因此“Stop The World”停顿的时间不得不变长。还有一个参数:-XX:CMSFullGCsBeforeCompaction,这个参数表示执行多少次不压缩(即不整理空间随便)Full GC后,进行一次带压缩的Full GC。

 

5.G1收集器(Garbage First)

G1是一款面向服务端应用的垃圾收集器,HotSpot团队的目标是,在将来能够用G1替换掉JDK1.5中发布的CMS收集器。与其余的收集器相比,G1收集器具备以下特色:

 

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优点,使用多个CPU(多个CPU或者是多个CPU核心)来缩短“Stop The World”的停顿时间,部分其余收集器本来须要停顿java线程执行的GC动做,G1收集器仍然能够经过并发的方式让java程序继续运行。
  • 分代收集:与其余收集器同样,分代概念在G1中仍然得意保留。虽然G1收集器不须要和其余的收集器配合就能管理整个GC堆,可是它可以采用不一样的方式去处理新建立的对象和已经存活了一段时间、熬过了屡次GC的旧对象,以期经过这种方式得到更好的垃圾回收的效果。
  • 空间整合:与CMS的“标记-清理”算法不一样,G1从总体来看是基于“标记-整理”算法实现的收集器,从局部上来看是基于“复制”算法实现的。无论使用这两种的哪一种,就不会产生内存碎片。
  • 可预测的停顿:这是G1对象CMS的另外一大优点,两者虽然都关注下降停顿时间,可是G1除了关注下降停顿时间,可是G1处理这点以外,还能够创建可预测的停顿时间模型,能让使用者明确指定一个长度在M毫秒的时间片断内,消耗在垃圾收集上的时间不得超过N毫秒。

 

在G1以前的其余收集器进行瘦的范围都是整个新生代和老年代,G1收集器java堆的内存布局与其余收集器有很大的差异,他将整个java堆分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔离的了,他们都是一部分Region(不须要连续)的集合。

G1的详解讲解会做为新的一章来分析,以上关于G1的描述摘抄子(深刻了解虚拟机),后面会有单独的一章来说G1收集器。

相关文章
相关标签/搜索