本文已收录至 Github,举荐浏览 👉 Java 随想录
微信公众号:Java 随想录
CSDN:码农 BookSea
转载请在文首注明出处,如发现歹意剽窃 / 搬运,会动用法律武器保护本人的权利。让咱们一起保护一个良好的技术创作环境!
平凡的开始—CMS
题目叫做平凡的开始。纵观全书,在垃圾回收器这一篇章,对于 CMS 和 G1 的笔墨是最多的。不可否认 CMS 垃圾收集器有许多毛病,任何一个 JDK 版本的默认垃圾收集器都不是 CMS,然而 CMS 收集器是 HotSpot 虚拟机谋求低进展的第一次胜利尝试,没有 CMS 可能就不会有前面的 G1,HotSpot 开发团队最后赋予 G1 的冀望是(在比拟长期的)将来能够替换掉 JDK 5 中公布的 CMS 收集器(这段是书中原话),G1 的产生排汇了很多 CMS 过往的教训。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器,应用的是标记 - 革除算法。
运作过程
CMS 整个过程分为四个步骤,包含:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 从新标记(CMS remark)
- 并发革除(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% 的空间后就会被激活 ,这是一个偏激进的设置,如果在理论利用中老年代增长并不是太快,能够适当调高参数-XX:CMSInitiatingOccu-pancyFraction
的值来进步 CMS 的触发百分比,升高内存回收频率,获取更好的性能。到了 JDK 6 时,CMS 收集器的启动阈值就曾经默认晋升至 92%。但这又会更容易面临另一种危险:要是 CMS 运行期间预留的内存无奈满足程序调配新对象的须要,就会呈现一次“并发失败”(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:解冻用户线程的执行,长期启用 Serial Old 收集器来从新进行老年代的垃圾收集,但这样进展工夫就很长了。所以参数 -XX:CMSInitiatingOccupancyFraction
设置得太高将会很容易导致大量的并发失败产生,性能反而升高,用户应在生产环境中依据理论利用状况来衡量设置。
内存碎片
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 时都进行碎片整顿)。