CMS垃圾收集器(Concurrent Mark Sweep)

刘超 8月前 ⋅ 1858 阅读   编辑

目录

  1、CMS介绍
  2、CMS实现(基于标记-清除算法实现),分为以下四步:
    a、初始标记
    b、并发标记
    c、重新标记
      - 为啥需要重新标记
    d、并发清除

  3、CMS优点
  4、CMS缺点
  5、空间分配担保(老年代)
  6、CMS收集器步骤

 

一、CMS介绍

  CMS(Concurrent Mark Sweep)收集器,以获取最短回收停顿时间(就是stop the world时间)为目标,多数应用于互联网站或者B/S系统的服务器上。

1、Concurrent(并发),描述的是垃圾收集线程和用户业务线程可以同时执行,不影响用户业务线程,但也不是绝对,在一些阶段上还是会出现stop the world这种情况,只不过stop the world时间不会很长
2、Mark(标记),标记的是有哪些对象依然有引用指向的(即是存活对象);哪些对象没有引用指向的(即是垃圾对象)
3、Sweep(清除),清除垃圾对象

二、CMS实现

  CMS是基于“标记-清除”算法实现的,从宏观来看,整个过程分为4个步骤:

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

  其中初始标记、重新标记这两步骤仍然需要“Stop The World”;初始标记这步就是标记一下GC Roots能直接关联到的对象,并不会沿着GC Roots链条一直往下走,只是找到GC Roots那个初始对象,所以速度很快;并发标记就是进行GC Roots Tracing的过程,GC Roots Tracing就是从GC Roots开始,找到被GC Roots引用或者关联的对象,接着从这个对象出发向后面寻找它能引用或者关联到其他的所有对象,这是个并发动作,并不会阻碍业务线程的执行;重新标记阶段则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

  为啥会有重新标记呢?

  在并发标记阶段,一方面GC线程去标记哪些对象是可以被GC Roots所引用到的,另一方面用户业务线程也可能在同一时刻在运行,既然是同时发生,那在标记时某个对象可能还没有被引用到,但由于业务线程还在执行,那这个线程可能导致有引用指向这个对象。反过来也是成立的,就是在并发标记阶段,某个对象已经是被引用的,但由于业务线程还在运行,这个线程可能导致没有引用指向这个对象了,所以会有重新标记阶段来纠正因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录。

  CMS收集器的运作步骤如下图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的。

 

  有如上图,有4个CPU内核,有4个线程在这4个核心上执行,紧接着GC线程进行初始标记,GC线程在初始标记阶段是没有用户线程的;接着GC线程进入并发标记阶段,在并发标记阶段是GC线程是可以和用户线程一起执行的,所以会有用户线程1、用户线程2、用户线程4,用户线程3就被GC标记线程替代了,用户线程3暂停了,因为总共只有4个CPU核心,某个时刻只能有4个线程在运行,GC标记线程已经占用了一个CPU核心,所以只会有3个业务线程在运行;再接下来GC线程进入重新标记阶段,在这个阶段也会触发stop the world,导致所有用户线程暂停了;完成重新标记后,GC线程进入并发清除阶段,GC并发清除线程也可以和用户线程一起执行;最后还有一个线程重置的过程,就是把CMS线程恢复到最开始的状态,它也是和用户线程一起执行的。理解上图,就很好理解什么时候出现stop the world,什么时候可以和用户线程一起执行

三、CMS优点

  并发收集、低停顿(这里的停顿指的停顿用户线程),Oracle公司的一些官方文档中也称之为并发低停顿收集器(Concurrent Low Pause Collector)

四、CMS缺点

  1、CMS收集器对CPU资源非常敏感。
  2、CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能。要是CMS运行期间预留的内存无法满足程序需要时,虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

  3、收集结束时会有大量空间碎片产生,空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前进行一次Full GC。CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行Full GC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长、

  什么是浮动垃圾

  在之前的判断过程中,判定某个对象不是垃圾,但是由于用户线程同时也在运行,所有就会导致判断的不准确的,可能在判断完之后清除之前,这个对象已经变成垃圾对象,这时候是没法再进行判断的,本来这个对象应该被回收,实际上这个对象是没有被回收掉,只能等到下次进行垃圾收集的时候进行回收,这就是浮动垃圾

五、空间分配担保(老年代)

  在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。当大量对象在Minor GC后依然存活,就需要老年代进行空间分配担保,把Survivor无法容纳的对象直接进入老年代。如果老年代判断剩余空间不足(根据以往每一次回收晋升到老年代对象容量的平均值做经验值),则进行一次 Full GC。

六、CMS收集器步骤

  下面是CMS垃圾收集完成过程,细分成以下7个步骤,从而尽量减少stop the world的发生

  步骤1:Initial Mark(有stop the world)
  步骤2:Concurrent Mark(可以和用户线程一起工作)
  步骤3:Concurrent Preclean(可以和用户线程一起工作)
  步骤4:Concurrent Abortable Preclean(可以和用户线程一起工作)
  步骤5:Final Remark(有stop the world)
  步骤6:Concurrent Sweep(可以和用户线程一起工作)
  步骤7:Concurrent Reset(可以和用户线程一起工作)

  下面详细介绍各个步骤:

  1、Initial Mark:这个是CMS两次stop-the-world事件的其中一次,这个阶段的目标是:标记哪些直接被GC Root引用或者年轻代存活对象所引用的老年代的所有对象(虽然堆空间被刻意分成新生代与老年代,但新生代与老年代极大可能出现互相引用的情况,新生代引用老年代这是没问题的)

 

  GC Roots即会指向新生代对象,也会指向老年代对象

  2、Concurrent Mark

  在这个阶段Garbage Collector会遍历老年代,然后标记所有存活的对象,它会根据上个阶段找到的GC Roots遍历查找。并发标记阶段,它会与用户的应用程序并发执行,并不是老年代所有的存活对象都会被标记,因为在标记期间用户的程序可能会改变一些引用

 

  在上面的图中,与阶段1的图进行对比,就会发现有一个对象的引用已经发生了变化

  3、Concurrent Mark(翻译成中文并发的预先清理)

  这也是一个并发阶段,与应用的线程并发运行,并不会stop应用的线程。在并发运行的过程中,一些对象的引用可能会发生变化,但是这种情况发生时,JVM会将包含这个对象的区域(Card)标记为Dirty,这就是Card Marking;在pre-clean阶段,那些能够从Dirty对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了。

  


注意:本文归作者所有,未经作者允许,不得转载

全部评论: 0

    我有话说: