本文 首发于 🍀 永浩转载 请注明 来源

30、【对线面试官】CMS垃圾回收器

今天还是来聊聊CMS垃圾收集器呗?

  • 如果用Seria和 Parallel系列的垃圾收集器:在垃圾回收的时,用户线程都会完全停止,直至垃圾回收结束!
  • CMS的全称: Concurrent Mark Sweep,翻译过来是「并发标记清除」

  • 用CMS对比上面的垃圾收集器( Seria和Parllel和 parNew):它最大的不同点就是「并发」:在GC线程工作的时候,用户线程「不会完全停止」,用户线程在「部分场景下」与GC线程一起并发执行
  • 无论是什么垃圾收集器, Stop The Word/是一定无法避免的!
  • CMS只是在「部分」的GC场景下可以让GC线程与用户线程并发执行
  • 目的:为了避免「老年代GC」出现「长时间」的卡顿( Stop The Word )

CMS工作流程

  • CMS可以简单分为5个步骤:初始标记、并发标记、并发预清理、重新标记以及并发清除

    • 从步骤可看出,CMS主要是实现了「标记清除」垃圾回收算法

    • 「初始标记」

      • 「初始标记」会标记 GCroots「直接关联」的对象以及「年轻代」指向「老年代」的对象
      • 「初始标记」这个过程是会发生 Stop The Word的。但这个阶段的速度算是很快的,因为没有「向下追溯」(只标记一层)

    • 「并发标记」

      • 「并发标记」这个过程是不会停止用户线程的(不会发生 Stop The Word)。这一阶段主要是从 GC Roots向下「追溯」,标记所有可达的对象
      • 并发标记」在GC的角度而言,是比较耗费时间的(需要追溯)

    • 「并发预处理」

      • 「并发预处理」这个阶段主要是:希望能减少下一个阶段「重新标记」所消耗的时间
      • 因为下一个阶段「重新标记」是需要Stop The World的,「并发标记」这个阶段由于用户线程是没有被挂起的,所以对象是有可能发生变化的
      • 可能有些对象,从新生代晋升到了老年代。可能有些对象,直接分配到了老年代(大对象)。可能老年代或者新生代的对象引用发生了变化

    • 「重新标记」

      • 「重新标记」阶段会 Stop The Word,这个过程的停顿时间其实很大程度上取决于上面「并发预处理」阶段
      • 这是一个追赶的过程:边在标记存活对象,一边用户线程在执行产生垃圾)

    • 「并发清除」

      • 一边用户线程在执行,一边GC线程在回收不可达的对象
      • 这个过程,还是有可能用户线程在不断产生垃圾,但只能留到下一次GC进行处 理了,产生的这些垃圾被叫做“浮动垃圾”
      • 完了以后会重置CMS算法相关的内部数据,为下一次GC循环做准备

为什么要扫年轻代?

  • CMS主要回收老年代的对象。年轻代有可能会指向老年代的对象,不扫就不知道是不是垃圾了

「并发预处理」问题解决

  • 针对老年代的对象,其实还是可以借助类 card table的存储(将老年代对象发生变化所对应的卡页标记为 dirty)
  • 所以「并发预处理」这个阶段会扫描可能由于「并发标记」时导致老年代发生变化的对象,会再扫描一遍标记为diy的卡页
  • 对于新生代的对象,我们还是得遍历新生代来看看在「并发标记」过程中有没有对象引用了老年代.
  • JVM里给我们提供了很多「参数」,有可能在这个过程中会触发一次minor GC(触发了 minor GC是意味着就可以更少地遍历新生代的对象)

相比G1,那你觉得CMS有什么缺点呢?

  • 1.空间需要预留:CMS垃圾收集器可以一边回收垃圾,一边处理用户线程,那需要在这个过程中保证有充足的内存空间供用户使用。

    • 如果CMS运行过程中预留的空间不够用了,会报错( Concurrent Mode Failure),这时会启动 Serial Old垃圾收集器进行老年代的垃圾回收,会导致停顿的时间很长
  • 2.内存碎片问题:CMS本质上是实现了「标记清除算法」的收集器(从过程就可以看得出),这会意味着会产生内存碎片

    • 由于碎片太多,又可能会导致内存空间不足所触发 full GC,CMS一般会在触发full GC这个过程对碎片进行整理
    • 整理涉及到「移动」/「标记」,那这个过程肯定会 Stop The Word的,如果内存足够大(意味着可能装载的对象足够多),那这个过程卡顿也是需要一定的时间的。
  • 使用CMS的弊端好像就是一个死循环

    • 1.内存碎片过多,导致空间利用率减低。
    • 2.空间本身就需要预留给用户线程使用,现在碎片内存又加剧了空间的问题,导致有可能垃圾收集器降级为 Serial old,卡顿时间更长
    • 3.要处理内存碎片的问题(整理),同样会卡顿

总结

  • CMS把垃圾回收的过程给"细分"了,然后在某些阶段可以不停止用户线程,一边回收垃圾,一边处理请求,来减少每次垃圾回收时 Stop The Word的时间

  • 中间也做了很多的优化( dirty card标记、可能中途触发 minor gca等等,在我理解下,这些都提供了CMS的相关参数配置

  • CMS垃圾回收器设计目的:

    • 为了避免「老年代 GC」出现「长时间」的卡顿(Stop The World)
  • CMS垃圾回收器回收过程:

    • 初始标记、并发标记、并发预处理、重新标记和并发清除。初始标记以及重新标记这两个阶段会Stop The World
  • CMS垃圾回收器的弊端:

    • 会产生内存碎片&&需要空间预留:停顿时间是不可预知的