JVM五:垃圾收集器(7种)

这里讨论的收集器基于JDK1.7Update 14之后的HotSpot虚拟机,这个虚拟机包含的所有收集器如下图所示

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。

一:Serial(串行GC)收集器

    Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,它只会使用一个CPU或者一条收集线程去完成垃圾收集作,而且必须暂停其他所有的工作线程(用户线程),直到它收集完成。

 Serial/Serial Old收集器运行示意图(表示Serial和Serial Old搭配使用)

     Serial收集器是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,简单高效,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率,因此是运行在Client模式下的虚拟机的不错选择(比如桌面应用场景)。

二:ParNew(并行GC)收集器(只是使用多线程进行回收垃圾)

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

ParNew/Serial Old收集器运行示意图(表示ParNew和Serial Old搭配使用)

    Parnew收集器是运行在Service模式下虚拟机中首选的新生代收集器,其中一个与性能无关的原因就是除了Serial收集器外,目前只有ParNew收集器能与CMS收集器配合工作。

   PreNew收集器在单CPU环境中绝对没有Serial的效果好,由于存在线程交互的开销,该收集器在超线程技术实现的双CPU中都不能一定超过Serial收集器。默认开启的垃圾收集器线程数就是CPU数量,可通过-XX:parallelGCThreads参数来限制收集器线程数

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

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

Parallel Scavenge/Parallel Old收集器运行示意图(表示Parallel Scavenge和Parallel Old搭配使用)

      短停顿时间适合和用户交互的程序,体验好。高吞吐量适合高效利用CPU,主要用于后台运算不需要太多交互。提供了两个参数来精确控制吞吐量:1.最大垃圾收集器停顿时间(-XX:MaxGCPauseMillis    大于0的毫秒数,停顿时间小了就要牺牲相应的吞吐量和新生代空间),2.设置吞吐量大小(-XX:GCTimeRatio    大于0小于100的整数,默认99,也就是允许最大1%的垃圾回收时间)。还有一个参数表示自适应调节策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。就不用手动设置新生代大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio)今生老年代对象大小(-XX:PretenureSizeThreshold),会根据当前系统的运行情况手机监控信息,动态调整停顿时间和吞吐量大小。也是其与PreNew收集器的一个重要区别,也是其无法与CMS收集器搭配使用的原因(CMS收集器尽可能地缩短垃圾收集时用户线程的停顿时间,以提升交互体验)。

四:Serial Old(串行GC)收集器

五:Parallel Old(并行GC)收集器

六:CMS(并发GC)收集器

         CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是HotSpot虚拟机中的一款真正意义上的并发收集器,第一次实现了让垃圾回收线程和用户线程(基本上)同时工作。用CMS收集老年代的时候,新生代只能选择Serial或者ParNew收集器。

CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤:

①.初始标记(CMS initial mark)停止所有线程

②.并发标记(CMS concurrenr mark)

③.重新标记(CMS remark)停止所有线程

④.并发清除(CMS concurrent sweep)

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

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

CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,器主要有三个显著缺点:

  1.CMS收集器对CPU资源非常敏感。在并发(并发标记、并发清除)阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致应用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。收集器线程所占用的CPU数量为:(CPU+3)/4=0.25+3/(4*CPU)。因此这时垃圾收集器始终不会占用少于25%的CPU,因此当进行并发阶段时,虽然用户线程可以跑,但是很缓慢,特别是双核CPU的时候,已经占用了5/8的CPU,吞吐量会很低。为了解决这种情况,产生了“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)。就是采用抢占方式来模拟多任务机制,就是在并发(并发标记、并发清除)阶段,让GC线程、用户线程交替执行,尽量减少GC线程独占CPU,这样垃圾收集过程更长,但是对用户程序影响小一些。实际上i-CMS效果很一般,目前已经被声明为“deprecated”。

  2.CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full  GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数提高性能。JDK1.6中,CMS收集器的启动阈值已经提升到92%。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。

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

G1收集器

     G1(Garbage First)收集器是JDK1.7提供的一个新的面向服务端应用的垃圾收集器,其目标就是替换掉JDK1.5发布的CMS收集器。其优点有:

  1.并发与并行:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或CPU核心)来缩短停顿(Stop The World)时间。

  2.分代收集:G1不需要与其他收集器配合就能独立管理整个GC堆,但他能够采用不同方式去处理新建对象和已经存活了一段时间、熬过多次GC的老年代对象以获取更好收集效果。

  3.空间整合:从整体来看是基于“标记-整理”算法实现,从局部(两个Region之间)来看是基于“复制”算法实现的,但是都意味着G1运行期间不会产生内存碎片空间,更健康,遇到大对象时,不会因为没有连续空间而进行下一次GC,甚至一次Full GC。

  4.可预测的停顿:降低停顿是G1和CMS共同关注点,但G1除了追求低停顿,还能建立可预测的停顿模型,可以明确地指定在一个长度为M的时间片内,消耗在垃圾收集的时间不超过N毫秒

  5.跨代特性:之前的收集器进行收集的范围都是整个新生代或老年代,而G1扩展到整个Java堆(包括新生代,老年代)。

那么是怎么实现的呢?

      1.如何实现新生代和老年代全范围收集:其实它的Java堆布局就不同于其余收集器,它将整个Java堆划分为多个大小相等的独立区域(Region),仍然保留新生代和老年代的概念,可是不是物理隔离的,都是一部分Region(不需要连续)的集合。

  2.如何建立可预测的停顿时间模型:是因为有了独立区域Region的存在,就避免在Java堆中进行全区域的垃圾收集,G1跟踪各个Region里面的垃圾堆积的价值大小(回收可以获得的空间大小和回收所需要的时间的经验值),后台维护一个优先队列,根据每次允许的收集时间,优先回收价值最大的Region(Garbage-First理念)。因此使用Region划分内存空间以及有优先级的区域回收方式,保证了有限时间获得尽可能高的收集效率。

  3.如何保证垃圾回收真的在Region区域进行而不会扩散到全局:由于Region并不是孤立的,一个Region的对象可以被整个Java堆的任意其余Region的对象所引用,在做可达性判定确定对象是否存活时,仍然会关联到Java堆的任意对象,G1中这种情况特别明显。而以前在别的分代收集里面,新生代规模要比老年代小许多,新生代收集也频繁得多,也会涉及到扫描新生代时也会扫描老年代的情况,相反亦然。解决:G1收集器Region之间的对象引用以及新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(分代的例子中就检查是否老年代对象引用了新生代的对象),如果是则通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中,当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可避免全堆扫描。

忽略Remembered Set的维护,G1的运行步骤可简单描述为:

①.初始标记(Initial Marking)

②.并发标记(Concurrenr Marking)

③.最终标记(Final Marking)

④.筛选回收(Live Data Counting And Evacution)

1.初始标记:初始标记仅仅标记GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新的对象。这阶段需要停顿线程,不可并行执行,但是时间很短

2.并发标记:此阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,此阶段时间较长可与用户程序并发执行。

3.最终标记:此阶段是为了修正在并发标记期间因为用户线程继续运行而导致标记产生变动的那一份标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这段时间需要停顿线程,但是可并行执行

4.筛选回收:对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来制定回收计划。

  如果现有的垃圾收集器没有出现任何问题,没有任何理由去选择G1,如果应用追求低停顿,G1可选择,如果追求吞吐量,和Parallel Scavenge/Parallel Old组合相比G1并没有特别的优势。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值