CMS垃圾收集器

简介

CMS全称叫做Concurrent Mark Sweep(并发标记清除),是一款以获取最短回收停顿时间为目标的老年代收集器,适合基于B/S系统的服务器上,系统停顿时间更短,给用户带来较好的体验。

它使用“标记-清除”算法,由于老年代存活对象较多,所以只需要清除少量的无效对象即可完成回收工作。

另外,CMS也是一款真正意义上的并发收集器,能够与用户线程同时进行。虽然,并发回收过程中也有几个阶段需要Stop the world,但是由于任务简单,所以停顿时间非常短。

可以通过-XX:+ UseConcMarkSweepGC来标识开启CMS收集器,会使用ParNew对新生代的无用对象进行回收。

新生代的垃圾回收

新生代的回收使用的是ParNew垃圾收集器,全称叫做并行新生代垃圾收集器,是SerialNew的多线程版本。在垃圾回收的过程中,需要STW,并且控制参数,收集算法,对象分配规则、回收策略等都与SerialNew相同。

ParNew采用的是“标记+复制”算法,由于每次垃圾回收时存活的新生对象较少,因此只需要付出少量对象的复制成本即可完成垃圾的收集。

另外,ParNew在回收结束后会产生一些日志,下面将对回收日志进行分析:

2016-08-23T02:23:07.219-0200: 64.322: [GC (Allocation Failure) 64.322: [ParNew: 613404K->68068K(613440K), 0.1020465 secs] 10885349K->10880154K(12514816K), 0.1021309 secs] [Times: user=0.78 sys=0.01, real=0.11 secs]
  1. 2016-08-23T02:23:07.219-0200::GC发生的时间;
  2. 64.322:距JVM启动64.322秒后发生了此次Minor GC;
  3. GC:表示发生的是Minor GC;
  4. Allocation Failure:发生GC的原因,是由于年轻代没有足够的空间分配了;
  5. ParNew:使用的是并行新生代收集器,使用多线程模式进行新生代对象进行回收,需要停止用户线程;
  6. 613404K->68068K:表示新生代的空间大小在MinorGC前后的使用状况,由 613404K 降为 68068K,并且这68068K的数据是存储到了Survivor1区。
  7. 613440K:新生代的空间大小;
  8. 0.1020465 secs:Duration for the collection who final cleanup;
  9. 10885349K->10880154K:整个堆的空间大小在垃圾收集前后的使用状况,由10885349K 减少为10880154K
  10. 12514816K:整个堆空间的大小;
  11. 0.1021309 secs:ParNew标记、复制活着的对象所花费的时间,以及与老年代通信开销、对象晋升到老年代时间、垃圾收集周期结束一些最后对象的清理所花费的时间;
  12. [Times: user=0.78 sys=0.01, real=0.11 secs]:GC事件的持续时间,通过多种维度来衡量:
    • user:此次垃圾回收,垃圾回收线程消耗的所有CPU时间;
    • sys:操作系统调用,以及等待所有系统事件的时间;
    • real:用于线程暂停的事件;

在新生代回收前,整个堆空间占用为10885349K,整个年轻代的空间消耗为613404K,可计算出老年代的空间消耗为10885349K - 613404K = 10271945K

新生代清理完毕后,整个堆消耗的空间大小为10880154K,整个年轻代消耗的空间大小(Survivor)为68068K,则可老年代消耗的空间大小为10880154K-68068K=10812086K

因此,从年轻代晋升到老年代的数据大小为10812086K - 10271945K=540141K,有613404K - 540141K - 68068K = 5195K的数据被回收。

老年代的垃圾回收

阶段一:初始标记

  1. 目的

    • 标记老年代中所有的GC Roots引用的对象;
    • 标记老年代中被年轻代中活着的对象引用的对象;

  2. 特点

    由于需要对所有的对象进行标记,为了防止标记过程中有对象状态发生改变,所以需要Stop the world,停止用户线程,但是整个标记的过程耗时短。

  3. 参数分析

    2016-08-23T11:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [ CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    • 2016-08-23T11:23:07.321-0200:GC发生的时间;
    • 64.425:距JVM启动64.425秒后发生了此次并发回收的初始标记阶段;
    • CMS Initial Mark:初始化标记阶段,开始标记所有的GC Roots和在老年代中被年轻代引用的对象;
    • 10812086K:老年代空间使用量;
    • 11901376K:整个老年代空间大小;
    • 10887844K:整个堆空间使用量;
    • 12514816K:整个堆空间大小;
    • 0.0001997 secs:初始标记阶段使用的时间;

阶段二:并发标记

  1. 目的

    从初始化标记阶段找到的GC Roots开始进行Tracing,找到所有的存活对象。

  2. 特点

    并发标记阶段会与用户线程同时进行,因此会有一些对象的引用状态发生改变。

  3. 参数分析

    2016-08-23T11:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
    2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]
    • CMS-concurrent-mark:该阶段会进行GC Roots的tracing,找到所有存活的对象;
    • 0.035/0.035 secs:该阶段的持续时间;

阶段三:并发预清理

  1. 目的

    标记在并发标记阶段引用发生变化的对象,如果发现对象的引用发生变化,则JVM会标记堆的这个区域为Dirty Card

    那些能够从Dirty Card到达的对象也被标记(标记为存活),当标记做完后,这个Dirty Card区域就会消失。

  2. 特点

    该阶段是一个并发阶段,能够与用户线程同时运行,不会中断他们。

  3. 参数分析

    2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
    2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
    • CMS-concurrent-preclean:重新标记在并发标记阶段引用发生改变的存活对象。

阶段四:并发可中断预清理

  1. 目的

    该阶段主要是为了Final Remark做准备,承担了Final Remark阶段足够多的工作。

  2. 特点

    那为什么又叫做可中断呢?是因为最终重新标记阶段不是并发的,所有用户线程进入final Remark阶段都要被停止。如果在这之前Minor GC刚刚结束,紧接着又发生Final Remark的话,会造成2次连续的停顿。因此,为了避免类似的连续停顿发生,使用可中断预处理就是为了缩短停顿的长度,避免连续停顿。

    可中断预清理阶段会等到新生代使用空间达到50%的时候才开始。理论上,离下次新生代垃圾回收还有半程的时间,这样CMS有足够的时间来避免连续停顿。并且CMS收集器会根据以往的历史记录推算出下一次新生代收集可能会持续的时间,所以估计快要发生新生代垃圾回收时,会立即停止预清理阶段。

  3. 参数分析

    2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
    2016-08-23T11:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean4: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]

阶段五:最终重新标记

  1. 目的

    完成标记整个老年代所有存活的对象。

  2. 特点

    由于之前预清理阶段是并发执行的,可能又会有对象的引用发生变化,并且该阶段需要停止用户线程

  3. 参数分析

    2016-08-23T11:23:08.447-0200: 65.550: [GC (CMS Final Remark5)
    [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: 
    [weak refs processing, 0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: 
    [scrub symbol table, 0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] 
    [Times: user=0.06 sys=0.00, real=0.01 secs]
    • CMS Final Remark:最终重新标记阶段,这个阶段会标记所有老年代存活的对象,包括在并发标记阶段发生变化,或者新创建的引用对象;
    • YG occupancy 387920 K (613440 K):年轻代空间使用情况;
    • Rescan (parallel):该子阶段在用户线程停止的之后完成标记工作;
    • weak refs processing:处理弱引用;
    • class unloading:进行无用类的卸载;
    • scrub symbol table:清理符号表;
    • CMS-remark: 10812086K(11901376K):在这个阶段之后,老年代占有的内存和容量大小;
    • 11200006K(12514816K):整个堆的使用内存和容量大小;
    • 0.0110730 secs:这个阶段持续的时间;

阶段六:并发清除

  1. 目的

    移除那些不同的对象,并回收占用的内存空间。

  2. 参数分析

    2016-08-23T11:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
    2016-08-23T11:23:08.485-0200: 65.588: [CMS-concurrent-sweep6: 0.027/0.027 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]

阶段七:并发重置

  1. 目的

    重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用;

  2. 参数分析

    2016-08-23T11:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
    2016-08-23T11:23:08.497-0200: 65.601: [CMS-concurrent-reset7: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

CMS出现的异常情况及调优处理

并发模式失效

  1. 发生的原因

    新生代发生垃圾回收,但是老年代没有足够的空间容纳来自新生代的晋升对象时,CMS就会退化为一个Full GC。所有用户线程将会被暂停,老年代中的所有无用对象将会被回收,并且该回收操作是单线程的,会导致用户程序长时间的卡顿。

  2. 优化策略

    • 如果内存足够大,那么最好、最简单的办法就是增大堆空间;

    • 给后台CMS线程更多的运行机会

      由于在CMS清理期间,老年代的剩余空间不足以容纳新生代的晋升对象,那么可以让CMS进行提前回收,并增加回收的频率。默认情况下,当老年代空间达到70%的时候,就会触发CMS。那么可以适当的减小该值,最简单的方法就是同时设置以下两个标志:-XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=70UseCMSInitiatingOccupancyOnly的值默认为false,需要设置为true,表示仅通过-XX:CMSInitiatingOccupancyFraction标志来决定何时开启CMS扫描;

      那么如何设置合适的CMSInitiatingOccupancyFraction值呢?

      具体方法是,在回收日志中寻找首次并发模式失效(concurrent mode failure)的标志,找到后再反向查找最近的CMS-initial-mark的记录,查看记录中老年代空间的占用情况:

      0.103: [GC (Allocation Failure) 0.103: [ParNew: 6827K->6827K(9216K), 0.0000273 secs]0.103: [CMS: 6146K->8195K(10240K), 0.0041618 secs] 12973K->12675K(19456K), [Metaspace: 3216K->3216K(1056768K)], 0.0042190 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
      0.108: [GC (CMS Initial Mark) [1 CMS-initial-mark: 8195K(10240K)] 14778K(19456K), 0.0003631 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
      0.108: [CMS-concurrent-mark-start]
      0.108: [Full GC (Allocation Failure) 0.108: [CMS0.109: [CMS-concurrent-mark: 0.000/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
       (concurrent mode failure): 8195K->8193K(10240K), 0.0029069 secs] 14942K->12674K(19456K), [Metaspace: 3222K->3222K(1056768K)], 0.0029273 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

      在这条记录中,我们可以知道当老年代空间占用达到8195/10240=80%的时候触发了此次CMS扫描。可以判断出这个值太大了,因此需要调整-XX:CMSInitiatingOccupancyFraction,将其设置为小于80%的某个值。

      那么是否可以将CMSInitiatingOccupancyFraction值设置为0或者其他比较小的值,让后台线程持续的运行呢?

      通常不推荐这样的设置,理由如下:

      1. 每个持续运行的后台CMS线程都会100%占用一个CPU,使得机器的CPU使用量增大,并且毫无目的的持续运行肯定会浪费宝贵的CPU资源。
      2. 应用线程会和这些持续运行的CMS线程竞争CPU时钟周期,并且CMS周期运行的越频繁,CPU周期越长,应用线程的停顿时间也就越长。而使用CMS的主要目的就是缩短GC停顿的时间,因此缩短CMS周期只会适得其反,最终降低了应用的性能。
    • 调整CMS后台线程数

      每个CMS后台线程都会100%的占用CPU时钟周期,如果此时发生并发模式失效,同时又有可用的CPU时钟周期,那么设置-XX:ConcGCThreads=N,适当的增大后台线程数。默认情况下,ConcGCThread是由ParallelGCThreads计算得到:

      ConcGCThreads=(3+ParallelGCThreads)/4 C o n c G C T h r e a d s = ( 3 + P a r a l l e l G C T h r e a d s ) / 4

      使用此设置的要点就是判断是否有可用的CPU周期。如果设置的偏大,那么CMS后台线程就会占用用户线程的CPU周期,导致应用程序轻微的停顿。

新生代对象晋升失败

  1. 发生原因

    由于CMS是基于“标记+清除”算法来回收老年代对象的,因此长时间运行后会产生大量的空间碎片问题。碎片过多,将会给大对象的分配带来麻烦。因此会出现这样的情况,老年代还有很多剩余的空间,但是找不到连续的空间来分配当前对象,这样不得不提前出发一次Full GC。

  2. 优化策略

    为了解决空间碎片问题,CMS收集器提供−XX:+UseCMSCompactAlFullCollection标志,用于在一次Full GC之后,附带一次碎片整理过程。但是整理是无法并发执行的,空间碎片问题没有了,但是有导致了连续的停顿。因此,可以使用另一个参数−XX:CMSFullGCsBeforeCompaction,表示在多少次不压缩的Full GC之后,对空间碎片进行整理。

CMS永久代调优

默认情况下,CMS不会对永久代进行垃圾回收,只有当永久代空间耗尽时,才会触发一次Full GC回收其中的垃圾对象。

此外,可以开启-XX:+CMSPermGenSweepingEnabled标志,让永久代与老年代使用同样的方式进行垃圾回收,但是触发永久代垃圾回收的指标与老年代是相互独立的,永久代使用-XX:CMSInitiatingPermOccupancyFraction标志,当永久代空间使用率达到此标志设定的值时,就会对永久代进行一次CMS扫描,并回收其无用的对象,该参数的默认值为80%。

另外,为了释放永久代中真正无用的类,包括其元数据,而不是仅仅释放少量无效的对象,应该开启-XX:+CMSClassUnloadingEnabled标志,在Java8中是默认开启的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值