乐趣区

关于后端:垃圾收集器必问系列CMS

本文已收录至 Github,举荐浏览 👉 Java 随想录

微信公众号:Java 随想录

CSDN:码农 BookSea

应该置信,本人是生存的战胜者。——雨果

纵观全书《深刻了解 JVM 虚拟机》第三版,在垃圾回收器这一篇章,对于 CMS 的笔墨是十分多的。CMS 收集器是 HotSpot 虚拟机谋求低进展的第一次胜利尝试,CMS 能够说是垃圾回收器的一个里程碑,其开启了 GC 回收器关注 GC 进展工夫的历史。

CMS 简介

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器,启用参数:-XX:+UseConMarkSweepGC,应用的是 标记 - 革除算法。在这之前的垃圾回收器,要么就是串行垃圾回收形式,要么就是关注零碎吞吐量。而 CMS 垃圾回收器的呈现,则突破了这个难堪的场面。JDK9 之后应用 CMS 垃圾收集器后,默认年老代就为 ParNew 收集器,并且不可更改,同时 JDK9 之后被标记为不举荐应用,JDK14 就被删除了。

CMS 垃圾回收器之所以可能实现对 GC 进展工夫的管制,其要害是 三色标记算法(不理解的同学去翻我之前写的文章),通过三色标记算法,实现了垃圾回收线程与用户线程并发执行,从而极大地升高了零碎响应工夫。

运作过程

CMS 整个过程分为四个步骤,包含:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 从新标记(CMS remark)
  4. 并发革除(CMS concurrent sweep)

其中初始标记、从新标记这两个步骤依然须要“Stop The World”

  • 初始标记仅仅只是标记一下 GC Roots 能间接关联到的对象,速度很快;
  • 并发标记阶段就是从 GC Roots 的间接关联对象开始遍历整个对象图的过程,这个过程是四个阶段中耗时最长的,然而不须要进展用户线程,能够与垃圾收集线程一起并发运行;
  • 而从新标记阶段则是为了修改并发标记期间,因用户程序持续运作而导致标记产生变动的那一部分对象的标记记录,这里应用的是增量更新,这个阶段的进展工夫通常会比初始标记阶段稍长一些,但也远比并发标记阶段的工夫短;
  • 最初是并发革除阶段,清理删除掉标记阶段判断的曾经死亡的对象,因为不须要挪动存活对象,所以这个阶段也是能够与用户线程同时并发的。

因为在整个过程中耗时最长的并发标记和并发革除阶段中,垃圾收集器线程都能够与用户线程一起工作,所以从总体上来说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

CMS 的缺点

CMS 有三个最大的毛病

处理器资源敏感

CMS 收集器是比拟耗费 CPU 资源的,对处理器资源是比拟敏感的。在并发阶段,它不会导致用户线程进展,但会占用了一部分线程(或者说处理器的计算能力)而导致应用程序变慢,升高总吞吐量。

低提早和高吞吐,往往无奈同时达成,低提早有时是就义高吞吐换得的,有得必有失。

CMS 默认启动的回收线程数是(处理器外围数量 +3)/4,也就是说,如果处理器外围数在四个或以上,并发回收时垃圾收集线程只占用不超过 25% 的处理器运算资源。然而当处理器外围数量有余四个时,CMS 对用户程序的影响就可能变得很大。如果利用原本的处理器负载就很高,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度突然大幅升高。

为了缓解这种状况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的 CMS 收集器变种,在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集线程的独占资源的工夫,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得较少一些,直观感触是速度变慢的工夫更多了,但速度降落幅度就没有那么显著。实践证明增量式的 CMS 收集器成果很个别,从 JDK 7 开始,i-CMS 模式曾经被申明为“deprecated”,即已过期不再提倡用户应用,到 JDK 9 公布后 i -CMS 模式被齐全废除。

无奈解决“浮动垃圾”

因为 CMS 收集器无奈解决“浮动垃圾”(Floating Garbage),有可能呈现“Con-current Mode Failure”失败进而导致另一次齐全“Stop The World”的 Full GC 的产生。

在 CMS 的并发标记和并发清理阶段,用户线程是还在持续运行的,程序在运行天然就还会随同有新的垃圾对象一直产生,但这一部分垃圾对象是呈现在标记过程完结当前,CMS 无奈在当次收集中解决掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”。

因为在垃圾收集阶段用户线程还须要继续运行,那就还须要预留足够内存空间提供给用户线程应用,因而 CMS 收集器不能像其余收集器那样期待到老年代简直齐全被填满了再进行收集,在 JDK5 的默认设置下,CMS 收集器当老年代应用了 68% 的空间后就会被激活。到了 JDK 6 时,CMS 收集器的启动阈值就曾经默认晋升至 92%,咱们能够通过 -XX:CMSInitiatingOccupancyFraction 参数自行调节。

但这又会更容易面临另一种危险:要是 CMS 运行期间预留的内存无奈满足程序调配新对象的须要,就会呈现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:解冻用户线程的执行,长期启用 Serial Old 收集器来从新进行老年代的垃圾收集,但这样进展工夫就很长了。

内存碎片

CMS 是一款基于“标记 - 革除”算法实现的收集器,在垃圾收集算法的时候咱们说过,标记 - 革除会产生 内存碎片。空间碎片过多时,将会给大对象调配带来很大麻烦,往往会呈现老年代还有很多残余空间,但就是无奈找到足够大的间断空间来调配以后对象,而不得不提前触发一次 Full GC 的状况。

为了解决这个问题,CMS 收集器提供了一个 -XX:+UseCMS-CompactAtFullCollection 开关参数(默认是开启的,此参数从 JDK 9 开始废除),用于在 CMS 收集器不得不进行 Full GC 时开启内存碎片的合并整顿过程,因为这个内存整理必须挪动存活对象。这样空间碎片问题是解决了,但进展工夫又会变长,因而虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-Compaction(此参数从 JDK 9 开始废除),这个参数的作用是要求 CMS 收集器在执行过若干次(数量由参数值决定)不整顿空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整顿(默认值为 0,示意每次进入 Full GC 时都进行碎片整顿)。


如果本篇博客有任何谬误和倡议,欢送给我留言斧正。文章继续更新,能够关注公众号第一工夫浏览。

退出移动版