CMS垃圾收集器

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CMS垃圾收集器相关的知识,希望对你有一定的参考价值。

注意:响应时间优先和吞吐量优先的区别!!!

①、CMS简介

  • CMS设计的初衷是为了提升应用程序的响应时间,也就是降低垃圾回收的时候STW的时间。所以CMS采用了并发标记并发清除,让垃圾收集线程和用户线程并发运行,从而实现提升程序响应时间,减少STW的目的。

  • CMS是什么:CMS全称叫Concurrent Mark Sweep从名字就能看出来这是一个并发收集并且采用标记-清除算法的垃圾收集器。一种以获取最短回收停顿时间为目标的老年代收集器

    • 注意标记是指将存活的对象和要回收的对象都给标记出来,而清除是指清除掉将要回收的对象。
  • 应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务

  • CMS 收集器的内存回收过程是与用户线程一起并发执行的,可以搭配 ParNew 收集器(多线程,新生代,复制算法)与 Serial Old 收集器(单线程,老年代,标记-整理算法)使用。

②、CMS工作流程

  1. 初始标记(有STW)
    1. 只标记GC Roots ,所以速度很快,但是仍存在 Stop The World问题。
  2. 并发标记
    1. 进行 GC Roots Tracing的过程,也就是从GC ROOT开始进行可达性分析,找出并标记存活对象,此时是和用户线程并发执行的,不会STW
  3. 重新标记(有STW)
    1. 为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。会存在 Stop The World 问题
    2. 上面的第一点怎么理解呢?由于并发标记阶段用户线程和GC垃圾回收线程在并行运行,所以在
  4. 并发清除
    1. 对垃圾对象进行清除回收。由于此时是和用户线程并发运行,所以在清除的过程中,可能任然会有新的垃圾产生,这些垃圾就叫[浮动垃圾]
    2. 如果当用户需要存入一个很大的对象时,新生代放不下去,老年代由于浮动垃圾过多,就会退化为 serial Old 收集器,将老年代垃圾进行标记-整理,当然这也是很耗费时间的!

③、CMS相关问题分析

从上面的流程中我们理解了CMS的工作流程分为4步:初始标记,并发标记重新标记并发清除。那么这4步流程中有没有存在什么问题呢???

问题一、为什么要重新标记

为什么要重新标记?因为并发标记是和用户线程并发执行的,在并发标记的过程中用户线程有可能又会新产生垃圾,或者已经被标记为垃圾的对象又重新被引用。

重新标记的时候难道是又从GC ROOT开始标记可达的对象吗?如果是这样的话,那么前面的并发标记的意义在哪里?如果不重新从GC ROOT 开始标记的话,那么垃圾收集器如何知道哪些是并发标记期间用户线程产生的垃圾呢???参考官方介绍:官方介绍

官网介绍


Pauses
The CMS collector pauses an application twice during a concurrent collection cycle.

The first pause is to mark as live the objects directly reachable from the roots (for example, object references from application thread stacks and registers, static objects and so on) and from elsewhere in the heap (for example, the young generation). This first pause is referred to as the initial mark pause.

The second pause comes at the end of the concurrent tracing phase and finds objects that were missed by the concurrent tracing due to updates by the application threads of references in an object after the CMS collector had finished tracing that object. This second pause is referred to as the remark pause.


翻译

CMS收集器在并发收集周期中暂停应用程序两次。

初始标记:第一次暂停是将从根(例如,来自应用程序线程堆栈和寄存器的对象引用、静态对象等等)和堆中其他地方(例如,年轻代)直接访问的对象标记为活动对象。第一个暂停被称为初始标记暂停。

重新标记:第二个暂停出现在并发跟踪阶段的末尾,并查找由于CMS收集器完成跟踪对象后,对象中的引用应用程序线程更新而错过的对象。第二个暂停被称为注释暂停。


总结

  • 也就是说重新标记过程中不会从GC ROOT开始去重新扫描,而是将并发标记过程中用户线程新产生的垃圾进行标记。
  • 关于重新标记时如何知道哪些是用户线程在并发标记期间产生的垃圾问题,这个问题我看网上有很多版本,基本都是说通过一个引用队列来实现。

问题二、并发清除时用户线程中用户线程又产生新垃圾问题

在并发清除时存在两个问题:

  • 在清除的过程中,在标记阶段有些对象被GC ROOT引用着,但是在并发清除的时候,这些对象没有被GC ROOT引用了。那么这些对象就变成了垃圾(也就是浮动垃圾)。由于在标记阶段这些对象已经被认为不是垃圾了。所以垃圾收集器在这次无法清理这些对象。

    • 解决方案:针对于这种情况,在并发清理时产生的垃圾,会放到下一轮垃圾回收的时候被回收。比较简单
  • 有些空间在标记阶段没有任何GC ROOT去引用他,这块空间被认为是垃圾。但是在并发清理阶段却发现有新的对象从新生代晋升到老年代,放在了这块原本认为是垃圾的空间中。

    • 解决方案:针对这种情况,在CMS垃圾收集器早就做了相关处理,CMS垃圾收集器不会等到老年代完全被填满了才进行垃圾回收。而是要预留一部分空间(JDK6中阈值默认是92%)给用户线程继续使用,在并发清理的时候,如果有新生代的对象晋升到老年代了,那么这些晋升的对象会被放在预留的空间中

🤔思考问题

有没有想过这样一个问题,在并发清除的时候我们知道会不断产生浮动垃圾,并且由新生代新晋升到老年代的对象会被放入到预留空间中。如果出现这样一种情况:在并发清理的同时,大量的新生代对象晋升到了老年代,并且产生了大量的浮动垃圾。导致老年代空间吃紧,后续还不断有大对象要向老年代晋升,这时老年代便容纳不下这些新晋升的对象了。怎么办😰????

问题三、CMS什么情况下会退化为serialOld

这里其实就是上面的思考问题的答案。在并发清理过程中,如果不断有新生代对象被晋升到老年代的预留空间中,并且老年代产生大量的浮动垃圾的时候。导致了老年代空间紧张。这时候CMS就会退化为serialOld收集器。serialOld工作时会暂停所有的用户线程,单线程进行老年代的垃圾回收。所以这样也会降低垃圾收集的效率,STW时间延长。这也是CMS的一个缺陷之一。

也就是垃圾收集的速度赶不上垃圾产生的速度!!!导致其他对象无法分配空间了。

问题四、CMS垃圾收集器造成的内存碎片问题如何解决

我们知道CMS采用的是标记-整理算法来清理垃圾,但是这种算法会导致内存碎片问题。那么CMS是如何解决的?

CMS提供了一个参数(-XX:UseCMS-CompactAtFullCollection),该参数控制在full Gc的时候压缩一下空间,整理内存碎片(需要stw)。

难道每次发送 Full GC 的时候都STW一下压缩一下内存?这样岂不是也会降低效率?

其实CMS还提供了一个参数(-XX:CMSFullGCsBeforeCompaction=n),可以指定几次full gc后开始对内存碎片进行压缩,避免频繁整理内存碎片而造成stw过长。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩,可自己指定。

CMS问题总结

  • CMS是老年代垃圾收集器,使用的是标记清除算法,有内存碎片问题,如何解决:
    • 使用-XX:UseCMS-CompactAtFullCollection参数开启在full Gc的时候压缩一下空间,整理内存碎片(需要stw)
    • 使用-XX:CMSFullGCsBeforeCompaction=n参数,可以指定几次full gc后开始对内存碎片进行压缩,避免频繁整理内存碎片而造成stw过长。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。
  • 当浮动垃圾过多的时候,CMS垃圾收集器退化为serialOld
  • 并不是在老年代满了的时候才出发GC,而是空间占用在达到指定的阈值以后就会触发。在触发GC过程中JVM会预留一部分空间,GC过程中用户线程新产生的对象就分配到预留空间中。
  • 重新标记时,对于并不会冲GC ROOT开始重新遍历整个老年代,而是只标记在并发标记过程中用户线程新产生的垃圾即可。
  • 要理解CMS垃圾收集器,需要考虑两个核心问题
    • 并发标记 / 清除 过程中如果有原本不是垃圾的对象变为垃圾了怎么解决?
    • 并发标记 / 清除 过程中如果有原本是垃圾的对象又被重新引用了怎么解决?

以上是关于CMS垃圾收集器的主要内容,如果未能解决你的问题,请参考以下文章

CMS垃圾收集器——重新标记的讨论

CMS垃圾收集器

深入浅出CMS垃圾收集器

深入浅出CMS垃圾收集器

CMS垃圾收集器——重新标记和浮动垃圾的思考

CMS垃圾收集器