CMS收集器
(-XX:+UseConcMarkSweepGC 标记-清除算法)
概述:
CMS,全称Concurrent Mark and Sweep,用于对年老代进行回收,目标是尽量减少应用的暂停时间,减少full gc发生的机率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代
CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停。
收集周期:
1) Initial Mark 初始标记
这个阶段的任务是标记老年代中被GC Roots直接可达和被年轻代对象引用的对象,这个阶段也是第一次STW发生的阶段
2) Concurrent Mark 并发标记
这个阶段主要是通过从初始标记阶段中寻找到的标记对象开始,遍历老年代并且标记所有存活着的对象
需要注意的是,并非所有在老年代中存活的对象都会被标记,因为程序在标记期间可能会更改引用(比如图中的Current obj,它是并发标记阶段伴随着程序一起被删除了引用的对象)
这个阶段与应用程序共同运行
3) Concurrent Preclean 执行预清理
注: 相当于两次 concurrent-mark. 因为上一次concurrent-mark耗时较长,会有从新生代晋升到老年代的对象出现,将其清理掉
这也是一个并发阶段,与应用程序的线程并行执行。并发标记阶段与应用程序同时运行时,一些对象的引用可能会被改变,一旦这种情况发生,JVM就会标记堆上面的这块包含了变化对象的区域(这个堆的区域被称为"Card",这种方式被称为"Card Marking")
在这个阶段,这些脏对象将会被声明,并且这些对象能够到达的对象也会被标记。这些Card将会在上面的工作完成之后被清理掉
此外,还将执行一些必要的整理和重新标记阶段的准备工作。
4) Concurrent Abortable Preclean 执行可中止预清理
这个阶段也是和程序线程并发执行的。它的工作就是尽可能地进行清理工作,以减少重新标记阶段的任务(即减少了STW的停顿时间)
这个阶段的持续时间取决于很多因素,因为它需要不断地做一些相同的工作,直到满足某个终止条件为止(比如一定的迭代次数、一定的有效工作量、一定的时间等等)
5) Final Remark 重新标记
这个阶段是第二次,也是最后一次STW。这个阶段的目的是标记在老年代中被标记的所有存活下来的对象。
6) Concurrent Sweep 并发清除
移除未使用的对象,并且回收其占用的空间。
7) Concurrent Reset 并发重置
重置CMS算法内部的数据结构,为下一个周期做准备
参考文档:https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep
CMS减少停顿的原理:
标记过程分三步:并发标记是最主要的标记过程,而这个过程是并发执行的,可以与应用程序线程同时进行,初始标记和重新标记虽然不能和应用程序并发执行,但这两个过程标记速度快,时间短,所以对应用程序不会产生太大的影响
最后并发清除的过程,也是和应用程序同时进行的,避免了应用程序的停顿。
CMS的特点:减少了应用程序的停顿时间,让回收线程和应用程序线程可以并发执行,它的回收并不彻底。因此CMS回收的频率相较其他回收器要高,频繁的回收将影响应用程序的吞吐量,空间碎片多。
CMS何时开始?
cms gc 通过一个后台线程触发,该线程随着堆一起初始化,触发机制是默认每隔2秒判断一下当前老年代的内存使用率是否达到阈值,如果高于某个阈值的时候将激发CMS。
两次STW的原因:
当虚拟机完成两次标记后,便确认了可以回收的对象。但是,垃圾回收并不会阻塞程序的线程,如果当GC线程标记好了一个对象的时候,此时程序的线程又将该对象重新加入了GC-Roots的“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象。
为了解决这个问题,虚拟机会在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。
这些特定的指令位置主要在:
1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置