深入理解Java虚拟机-垃圾收集器

本章章节目录:

  Serial(串行GC)收集器

  ParNew(并行GC)收集器

  Parallel Scavenge(并行回收GC)收集器 

  CMS(Concurrent Mark Sweep)收集器



Serial(串行GC)收集器 :

  Serial收集器是一个新生代收集器,单线程执行,使用复制算法。

   优点:效率高,稳定。

   缺点:停顿时间长。

   参数设置:-XX:+UseSerialGC 

blob.png

     它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。 

ParNew(并行GC)收集器 :

    ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。 

 参数设置:-XX:+ UseParNewGC:

    新生代并行/老年代串行

   采用的是复制算法,需要多线程多核支持,可以通过 -XX:ParallelGCThreads 限制线程数量 

blob.png

   

   Parallel Scavenge(并行回收GC)收集器 

   Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。 

blob.png

参数设置:

    -XX:+UseParallelGC 

   使用Parallel收集器+ 老年代串行

    -XX:+UseParallelOldGC

   使用Parallel收集器+ 并行老年代

     同时还有两个参数:

       -XX:MaxGCPauseMills

      最大停顿时间,单位毫秒

    GC尽力保证回收时间不超过设定值 

        -XX:GCTimeRatio  0-100的取值范围 垃圾收集时间占总时间的比 默认99,即最大允许1%时间做GC


CMS(并发GC)收集器

      CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于“标记-清除”算法实现的 老年代收集器,整个收集过程大致分为4个步骤:

blob.png

    ①.初始标记(CMS initial mark)

    ②.并发标记(CMS concurrenr mark)

    ③.重新标记(CMS remark)

    ④.并发清除(CMS concurrent sweep)

    其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。

    由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

    CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,

   主要有三个显著缺点:

   CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。

    CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full  GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,

即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。

    最后一个缺点,CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full  GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full  GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full  GC之后,跟着来一次碎片整理过程。

   其他Cms参数配置:

   -XX:+ UseCMSCompactAtFullCollection  Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长

  -XX:+CMSFullGCsBeforeCompaction  设置进行几次Full GC后,进行一次碎片整理 ,-XX:ParallelCMSThreads 设定CMS的线程数量

 

G1收集器:

    在G1中,堆被划分成 许多个连续的区域(region)。每个区域大小相等,在1M~32M之间。JVM最多支持2000个区域,可推算G1能支持的最大内存为2000*32M=62.5G。区域(region)的大小在JVM初始化的时候决定,也可以用-XX:G1HeapReginSize设置。在G1中没有物理上的Yong(Eden/Survivor)/Old Generation,它们是逻辑的,使用一些非连续的区域(Region)组成的。

总结:

垃圾收集(Garbage Collection):

    新生代的GC叫YongGC,也叫MinorGC,指发生在新生代的垃圾回收动作,因为java具备朝生夕灭特性,所以YongGC非常频繁,一般回收集比较快;老年代GC叫FullGC,也叫Major GC,一般都伴有YongGC,GC的速度一般比YongGC慢10倍以上。目前虚拟机实现都是分代收集(G1物理上是不连续的,是逻辑分代,这里主要以jdk1.7之前为例),当要给对象分配空间时,在Eden上分配空间,如果空间不够,则触发一次YongGC,如果空间够,则分配空间,如果还不够则直接进入老年代;当一次YongGC后,从Eden,From Survivor的对象放入To Survivor,如果放不下,则进入老年代;每次Yong GC 后还留在Survivor中的对象,对象的年龄Age加1,达到一定年龄(默认为15,可用参数-XX:MaxTenuringThreshold设置)后自动进入老年代;在发生Yong GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于则看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。

统一的GC参数整理:

    -XX:+UseSerialGC:在新生代和老年代使用串行收集器

    -XX:SurvivorRatio:设置eden区大小和survivior区大小的比例

    -XX:NewRatio:新生代和老年代的比

    -XX:+UseParNewGC:在新生代使用并行收集器

    -XX:+UseParallelGC :新生代使用并行回收收集器

    -XX:+UseParallelOldGC:老年代使用并行回收收集器

    -XX:ParallelGCThreads:设置用于垃圾回收的线程数

    -XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器

    -XX:ParallelCMSThreads:设定CMS的线程数量

    -XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发

    -XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理

    -XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩

    -XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收

    -XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收

    -XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

调优总结:

  年轻代大小选择: 

         响应时间优先的应用: 年轻代可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。 

        吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

  年老代大小选择 

      响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎 片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

      并发垃圾收集信息、持久代并发收集次数 、传统GC信息、 花在年轻代和年老代回收上的时间比例、 减少年轻代和年老代花费的时间,一般会提高应用的效率 。

      吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

   JDK的版本选择:

            JDK7的性能优化,比JD6的性能上有所提升。

  

   参考文章:

      CMS垃圾收集器

  G1垃圾收集器

  JVM性能调优参考资料: 《深入理解Java虚拟机》-周志明





发表评论