垃圾回收器

Posted ken-jl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了垃圾回收器相关的知识,希望对你有一定的参考价值。

HotSpot虚拟机中有7种垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。

垃圾收集器是垃圾回收算法(标记-清除算法、复制算法、标记-整理算法、分代收集)的具体实现,不同商家、不同版本的JVM所提供的垃圾收集器可能会有很在差别,此处主要介绍HotSpot虚拟机中的垃圾收集器。

JDK7/8后,HotSpot虚拟机所有收集器及组合(连线),如下图:

 

 

 

由上图可知

新生代收集器:Serial、ParNew、Parallel Scavenge;

老年代收集器:Serial Old、Parallel Old、CMS;

    整堆收集器:G1;

两个回收器间有连线,表明他们可以搭配使用。

 

并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;

如ParNew、Parallel Scavenge、Parallel Old;

并发:指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);

         用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上; 

如CMS、G1(也有并行);

 

各个垃圾回收器应用场景:

Serial收集器

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;JDK1.3.1前是HotSpot新生代收集的唯一选择;

 

应用场景:是HotSpot在Client模式下默认的新生代收集器;也有优于其他收集器的地方;  简单高效(与其他收集器的单线程相比); 对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的。

设置参数: "-XX:+UseSerialGC":添加该参数来显式的使用串行垃圾收集器;

ParNew收集器

ParNew垃圾收集器是Serial收集器的多线程版本。

 

应用场景:在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。

设置参数

      "-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;

      "-XX:+UseParNewGC":强制指定使用ParNew;    

      "-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;

Parallel Scavenge收集器

Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。

应用场景

      高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;

      当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;

      例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序;

设置参数

      Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:

(A)"-XX:MaxGCPauseMillis"

      控制最大垃圾收集停顿时间,大于0的毫秒数;

      MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;

      因为可能导致垃圾收集发生得更频繁;

(B)"-XX:GCTimeRatio"

      设置垃圾收集时间占总时间的比率,0<n<100的整数;

      GCTimeRatio相当于设置吞吐量大小;

      垃圾收集执行时间占应用程序执行时间的比例的计算方法是:

      1 / (1 + n)

      例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%=1/(1+19);

      默认值是1%=1/(1+99),即n=99;

垃圾收集所花费的时间是年轻代和老年代收集的总时间;

如果没有满足吞吐量目标,则增加代的内存大小以尽量增加用户程序运行的时间;

 此外,还有一个值得关注的参数:

(C)、"-XX:+UseAdptiveSizePolicy"

开启这个参数后,就不用手工指定一些细节参数,如:

      新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等;

      JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomiscs);    

      这是一种值得推荐的方式:

      1、只需设置好内存数据大小(如"-Xmx"设置最大堆);

      2、然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"给JVM设置一个优化目标;

      3、那些具体细节参数的调节就由JVM自适应完成;        

      这也是Parallel Scavenge收集器与ParNew收集器一个重要区别;   

 

上面都是新生代的,下面接着介绍老年代的回收器

Serial Old收集器

 Serial Old是 Serial收集器的老年代版本;

应用场景

      主要用于Client模式;

      而在Server模式有两大用途:

      (A)、在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);

      (B)、作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用(后面详解);

Parallel Old收集器

Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本;JDK1.6中才开始提供;

JDK1.6及之后用来代替老年代的Serial Old收集器; 特别是在Server模式,多CPU的情况下;

这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合;

设置参数

      "-XX:+UseParallelOldGC":指定使用Parallel Old收集器;

CMS收集器

并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;

应用场景

      与用户交互较多的场景;        

      希望系统停顿时间最短,注重服务的响应速度;

      以给用户带来较好的体验;

      如常见WEB、B/S系统的服务器上的应用;

设置参数

      "-XX:+UseConcMarkSweepGC":指定使用CMS收集器;

CMS收集器3个明显的缺点

(A)、对CPU资源非常敏感

       并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。

      CMS的默认收集线程数量是=(CPU数量+3)/4;

      当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。

      增量式并发收集器:

      针对这种情况,曾出现了"增量式并发收集器"(Incremental Concurrent Mark Sweep/i-CMS);

      类似使用抢占式来模拟多任务机制的思想,让收集线程和用户线程交替运行,减少收集线程运行时间;

      但效果并不理想,JDK1.6后就官方不再提倡用户使用。

 

(B)、无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败

1、浮动垃圾(Floating Garbage)

      在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;

      这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;

      也要可以认为CMS所需要的空间比其他垃圾收集器大;

      "-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;

      JDK1.5默认值为68%;

      JDK1.6变为大约92%;               

2、"Concurrent Mode Failure"失败

      如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败;

      这时JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生;

      这样的代价是很大的,所以CMSInitiatingOccupancyFraction不能设置得太大。

(C)、产生大量内存碎片

      由于CMS基于"标记-清除"算法,清除后不进行压缩操作;

      前面《Java虚拟机垃圾回收(二) 垃圾回收算法》"标记-清除"算法介绍时曾说过:

      产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。

      解决方法:                

1、"-XX:+UseCMSCompactAtFullCollection"

      使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理过程;

      但合并整理过程无法并发,停顿时间会变长;

      默认开启(但不会进行,结合下面的CMSFullGCsBeforeCompaction);

2、"-XX:+CMSFullGCsBeforeCompaction"

      设置执行多少次不压缩的Full GC后,来一次压缩整理;

      为减少合并整理过程的停顿时间;

      默认为0,也就是说每次都执行Full GC,不会进行压缩整理;

      由于空间不再连续,CMS需要使用可用"空闲列表"内存分配方式,这比简单实用"碰撞指针"分配内存消耗大;

 

G1收集器

G1(Garbage-First)是JDK7-u4才推出商用的收集器;

应用场景

      面向服务端应用,针对具有大内存、多处理器的机器;

      最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;

      如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;

            

      用来替换掉JDK1.5中的CMS收集器;

      在下面的情况时,使用G1可能比CMS好:

      (1)、超过50%的Java堆被活动数据占用;

      (2)、对象分配频率或年代提升频率变化很大;

      (3)、GC停顿时间过长(长于0.5至1秒)。

      是否一定采用G1呢?也未必:

      如果现在采用的收集器没有出现问题,不用急着去选择G1;

      如果应用程序追求低停顿,可以尝试选择G1;

      是否代替CMS需要实际场景测试才知道。

设置参数

      "-XX:+UseG1GC":指定使用G1收集器;

      "-XX:InitiatingHeapOccupancyPercent":当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;

      "-XX:MaxGCPauseMillis":为G1设置暂停时间目标,默认值为200毫秒;

      "-XX:G1HeapRegionSize":设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region;

以上是关于垃圾回收器的主要内容,如果未能解决你的问题,请参考以下文章

JVM之G1垃圾回收器

JVM垃圾回收篇(垃圾回收器基本概述)

JVM垃圾回收篇(垃圾回收器基本概述)

JVM专题--垃圾回收算法, 垃圾回收器

5、垃圾回收机制

JVM垃圾回收器