JVM中的垃圾收集器 -- CMS

 

一、概述

  • 简介:

    • CMS收集器是一种以获取最短回收停顿为目标的收集器。
    • 由于GC线程在耗时最长的并发标记阶段、并发清除阶段、并发重设阶段都是与用户线程一起工作的,所以从总体上来说,CMS收集器的gc线程是与用户线程并发执行的。
  • 触发时机:

    • 第一次启动的时机:

      • 老年代占用率达到CMSInitiatingOccupancyFraction值(默认为92%)时,jvm会启动第一次CMS GC。
    • 之后启动的时机:

      • jvm自动判断。
    • 参数:

      • -XX:+UseCMSInitiatingOccupancyOnly
        • 命令JVM不要根据运行时收集的数据来判断什么时候开始gc,而是根据CMSInitiatingOccupancyFraction的值来判断是否进行CMS收集,一般不会打开该开关。
      • -XX:CMSInitiatingOccupancyFraction
        • 设置触发第一次CMS GC的阈值,老年代占用率达到该值时,jvm会启动第一次CMS GC,默认为92%。
  • 收集算法:

    • 使用标记-清除算法。
  • GC类型:

    • major gc:只收集老年代。
  • 收集范围

    • 整个老年代。

 

二、CMS GC过程:  

1、初始标记(CMS-initial-mark):

  • 标记对象:

    • 一个对象被标记,说明这个对象还存活着,不能被收集器回收。
  • 哪些对象会被标记:

    • 老年代中所有被根对象(GC Roots)直接引用的对象会被标记。
    • 老年代中被年轻代中存活对象直接引用的对象会被标记。
  • 标记方式:

    • 该阶段需要Stop the word,因为仅标记少量节点,所以该阶段带来的暂停时间很短。
  • 图示:

    CMS initial mark

2、并发标记(CMS-concurrent-mark):

  • 哪些对象会被标记:

    • 从初始标记阶段标记的对象出发,沿着引用链往下检索(RootsTracing),标记出引用链上所有老年代中存活的对象。
  • 标记方式:

    • gc线程和应用程序线程是并发执行的,故在并发标记期间可能存在对象的漏标和错标的问题。
  • 对象漏标:

    • 场景1:在并发标记期间有新的对象进入到老年代(新生代的对象晋升到老年代、在老年代中直接分配对象等),这些新进入老年代的对象没有被标记。
    • 场景2:在并发标记期间用户线程修改了存活对象A的某个字段:将这个字段指向了一个未被标记过的对象B,那么此时对象B就由(并发标记开始时的)不可达变为可达了,但是对象B却没有被标记。
  • 对象错标:

    • 某个存活对象(如下图的current obj)的某个字段引用着对象A,在并发标记期间用户线程将该字段修改为引用对象B,那么此时对象A就由(并发标记开始时的)可达变为不可达了,但是对象A却已经被标记过了。

      CMS concurrent marking

  • 对象漏标和错标的解决:

    • 在并发标记阶段,一些对象的引用可能已经发生了变化(导致对象漏标或错标),jvm会将这些引用发生变化的对象(包括新进入老年代的对象)所在的Card标记为Dirty Card。

      CMS dirty cards

 

3、并发预清理(CMS-concurrent-preclean): 

  • 哪些对象会被标记:

    • 从Dirty Card中包含的对象开始,沿着引用链往下检索(RootsTracing),标记出引用链上所有老年代中存活的对象。

  • 标记方式:

    • gc线程和应用程序线程并发执行。
    • 并发预清理的目的是为了减少重新标记阶段的工作,进而减少STW的时间。
  • Dirty Card恢复成正常的Card:

    • 当Dirty Card中所有对象的引用链都检索完成后,这个Card的Dirty标识就被清除了。

      CMS concurrent preclean

 

4、可中断的并发预清理阶段( CMS-concurrent-abortable-preclean):

  • 触发条件:

    • 在并发预清理阶段执行完成之后,如果eden区的占用量大于CMSScheduleRemarkEdenSizeThreshold(默认为2M) ,则会触发本阶段执行。
  • 中断条件:

    • eden区的占用量大于CMSScheduleRemarkEdenPenetration(默认50%),则中断本阶段。
    • 本阶段执行时间大于 CMSMaxAbortablePrecleanTime,则中断本阶段。
  • 哪些对象会被标记:

    • 和并发预清理阶段标记的对象一样。
  • 标记方式:

    • gc线程和应用程序线程并发执行,gc线程的标记工作可以被中断。
    • 在可中断的并发预清理阶段,jvm期望发生一次minor gc,这样年轻代中无用的对象就被回收掉了,进而可以减少remark阶段扫描对象的数量。
    • 并发预清理的目的同样也是为了减少重新标记阶段的工作,进而减少STW的时间。
  • 参数:

    • -XX:+CMSScheduleRemarkEdenSizeThreshold    
      •  eden区占用量大于该值时(默认为2M),触发可中断的并发预清理阶段启动。
    • -XX:CMSScheduleRemarkEdenPenetration
      • 表示eden区使用比例超过制定比例就结束该阶段进入remark阶段。
      • 默认50%,即:-XX:CMSScheduleRemarkEdenPenetration=50
    • -XX:CMSMaxAbortablePrecleanTime
      • 如果可中断的预清理执行时间超过该值,那么无论minor gc有没有发生,该阶段都会立即结束,然后进入remark阶段,这样是为了避免因没有等到minor gc而陷入无限等待。
      • 默认5秒,即:-XX:CMSMaxAbortablePrecleanTime=5000

 

5、重新标记(CMS-remark):

  • 哪些对象会被标记:

    • 重新标记在并发标记期间遗漏的对象。  
    • 新生代对象可能持有老年代中对象的引用,故CMS-remark阶段扫描对象的范围是整个堆,故堆(新生代+老年代)中对象的数目影响了Remark阶段耗时。
      • 新生代:扫描新生代中所有的区域。
      • 老年代:根据gc roots沿着引用链扫描 。
  • 标记方式:

    • 因为并发预清理是并发执行的,所以对象的引用可能会发生进一步的改变,故jvm在该阶段需要STW,以确保在清理之前保持一个正确的对象引用视图。
    • 一般重新标记阶段的暂停时间是比较长的。
  • 参数:

    • -XX:+CMSScavengeBeforeRemark
      • 在remark前强制进行一次minor gc,这样在一定程度上降低了重新标记阶段对“遗漏”对象的扫描时间。
    • -XX:+CMSParallelRemarkEnabled
      • 可以并行remark,减少暂停的时间

6、并发清除(CMS-concurrent-sweep):

  • 哪些对象会被清除:

    • 未被标记的对象。
  • 清除方式:

    • gc线程和应用程序线程并发执行。
    • 使用标记-清除法回收清除老年代的垃圾对象(未被标记的对象)

      CMS concurrent sweep

  • 浮动垃圾:

    • 概念:在并发清除阶段产生的垃圾称为浮动垃圾。
    • 说明:浮动垃圾在本次gc中是无法清除的,只能等到下次清理。

7、并发重设(CMS-concurrent-reset):

  • 工作内容:

    • 对记录标记对象的表等数据结构做重置处理,为下一次GC做准备。
  • 工作方式:

    • 并发重设线程和应用程序线程是并发执行的。

 

三、CMS GC失败后的预案:

  • 如果CMS运行期间预留的内存无法满足程序需要,那么就会出现一次“Concurrent Mode Failure”失败,此时JVM将启动后备预案:
  • 使用Serial Old收集器重新对老年代进行gc,这样一来,停顿时间就会很长。

 

四、CMS GC的两种模式:

  • background模式:

    • 触发条件:old的内存占比超过多少的时候就可能触发一次background式的cms gc
    • 其中background顾名思义是在后台做的,也就是可以不影响正常的业务线程跑,这个过程会经历CMS GC的所有阶段,该暂停的暂停,该并行的并行,效率相对来说还比较高,毕竟有和业务线程并行的gc阶段;
  • foreground模式:

    • 触发条件:比如业务线程请求分配内存,但是内存不够了,于是可能触发一次cms gc
    • 这个过程就必须是要等内存分配到了线程才能继续往下面走的,因此整个过程必须是STW的,因此CMS GC整个过程都是暂停应用的,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,这些省下来的阶段主要是并行阶段,Precleaning、AbortablePreclean,Resizing这几个阶段都不会经历,其中sweep阶段是同步的,但不管怎么说如果走了类似foreground的cms gc,那么整个过程业务线程都是不可用的,效率会影响挺大。
  • 参考:JVM源码分析之SystemGC完全解读 - 你假笨

 

五、一次CMS GC耗时统计

 

六、CMS收集器的替代品

JVM中的垃圾收集器

 

 参考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章