CMS(Concurrent Mark Sweep)收集器是以获取最短GC停顿时间为目标的收集器,它在垃圾收集时使得用户线程和GC线程能够并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿,多数应用于互联网网站或者B/S系统的服务端上。
CMS收集器是一个里程碑式的收集器,之前的收集器都是并行收集器,从CMS收集器开始后的都是并发收集器,但是CMS的缺点非常多,以至于所有的JDK版本没有一个将CMS收集器设置为默认的收集器。
长期来看,CMS收集器是要被G1等垃圾收集器替换掉的。在JDK8之后,使用它将会抛出一个警告。
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
CMS收集器的回收过程分为下列4步:
优点:
缺点:
-XX:CMSInitiatingOccupancyFraction
来指定,默认值为-1。/** * 演示CMS垃圾收集器日志的输出 * * -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseConcMarkSweepGC */
public class CMSDemo {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws InterruptedException {
byte[] b1 = new byte[4 * _1MB];
System.out.println("----111----");
byte[] b2 = new byte[4 * _1MB];
System.out.println("----222----");
byte[] b3 = new byte[4 * _1MB];
System.out.println("----333----");
byte[] b4 = new byte[2 * _1MB];
System.out.println("----444----");
}
}
日志输出如下:
----111----
2021-02-06T16:44:45.722+0800: 0.098: [GC (Allocation Failure) 2021-02-06T16:44:45.722+0800: 0.098: [ParNew: 5424K->581K(9216K), 0.0146141 secs] 5424K->4678K(19456K), 0.0146905 secs] [Times: user=0.03 sys=0.02, real=0.02 secs]
----222----
2021-02-06T16:44:45.745+0800: 0.121: [GC (Allocation Failure) 2021-02-06T16:44:45.745+0800: 0.121: [ParNew: 4918K->330K(9216K), 0.0025120 secs] 9015K->9090K(19456K), 0.0025765 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.748+0800: 0.124: [GC (CMS Initial Mark) [1 CMS-initial-mark: 8760K(10240K)] 13186K(19456K), 0.0003743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.749+0800: 0.125: [CMS-concurrent-mark-start]
----333----
----444----
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-preclean-start]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-abortable-preclean-start]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.750+0800: 0.126: [GC (CMS Final Remark) [YG occupancy: 6768 K (9216 K)]2021-02-06T16:44:45.750+0800: 0.126: [Rescan (parallel) , 0.0004946 secs]2021-02-06T16:44:45.751+0800: 0.127: [weak refs processing, 0.0000192 secs]2021-02-06T16:44:45.751+0800: 0.127: [class unloading, 0.0002698 secs]2021-02-06T16:44:45.751+0800: 0.127: [scrub symbol table, 0.0001761 secs]2021-02-06T16:44:45.751+0800: 0.127: [scrub string table, 0.0000488 secs][1 CMS-remark: 8760K(10240K)] 15528K(19456K), 0.0010916 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.751+0800: 0.127: [CMS-concurrent-sweep-start]
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-reset-start]
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 6932K [0x04800000, 0x05200000, 0x05200000)
eden space 8192K, 80% used [0x04800000, 0x04e72980, 0x05000000)
from space 1024K, 32% used [0x05000000, 0x05052908, 0x05100000)
to space 1024K, 0% used [0x05100000, 0x05100000, 0x05200000)
concurrent mark-sweep generation total 10240K, used 8759K [0x05200000, 0x05c00000, 0x05c00000)
Metaspace used 144K, capacity 2280K, committed 2368K, reserved 4480K
下面对上面的日志进行具体分析。
2021-02-06T16:44:45.722+0800: 0.098: [GC (Allocation Failure) 2021-02-06T16:44:45.722+0800: 0.098: [ParNew: 5424K->581K(9216K), 0.0146141 secs] 5424K->4678K(19456K), 0.0146905 secs] [Times: user=0.03 sys=0.02, real=0.02 secs]
2021-02-06T16:44:45.722+0800
:GC开始时间,由参数-XX:+PrintGCDateStamps
控制。
0.098
:GC开始,相对JVM启动的相对时间,单位是秒。
GC
:区别MinorGC和FullGC的标识,这次代表的是MinorGC。
Allocation Failure
:MinorGC发生的原因,在这里时由于年轻代不满足申请的空间,因此触发了MinorGC。
ParNew
:新生代使用的垃圾收集器的名称。
5424K->581K
:收集前后年轻代的使用情况。
9216K
:整个年轻代的容量。
0.0146141 secs
:这个解释用原滋原味的解释:Duration for the collection w/o final cleanup.
5424K->4678K
:收集前后整个堆的使用情况。
19456K
:整个堆的容量。
0.0146905 secs
:ParNew收集器标记和复制年轻代活着的对象所花费的时间(包括和老年代通信的开销、对象晋升到老年代时间、垃圾收集周期结束一些最后的清理对象等的花销)。
user=0.03 sys=0.02, real=0.02 secs
:GC事件的持续时间,通过多种分类来进行衡量:
user
:此次垃圾回收, 垃圾收集线程消耗的所有CPU时间(Total CPU time).
sys
:操作系统调用(OS call) 以及等待系统事件的时间(waiting for system event)
real
:应用程序暂停的时间(Clock time). 由于串行垃圾收集器(Serial Garbage Collector)只会使用单个线程, 所以real time等于user以及system time的总和,多线程下并不会相等。
这是CMS回收过程中两次STW中的一次,这个阶段主要是找出所有GCROOTs所直接引用的对象,以及新生代中对象所引用的老年代对象。
2021-02-06T16:44:45.748+0800: 0.124: [GC (CMS Initial Mark) [1 CMS-initial-mark: 8760K(10240K)] 13186K(19456K), 0.0003743 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
CMS Initial Mark
:初始标记阶段,收集所有的GC Roots和新生代直接引用的老年代对象。8760K
:当前老年代使用情况。10240K
:老年代的容量。13186K
:当前整个堆的使用情况。19456K
:整个堆的容量。这个阶段会根据上一个阶段中找到的对象开始遍历老年代的所有对象,然后标记出存活的对象。并发标记阶段垃圾回收线程和用户线程并发执行,并不是老年代所有存活的对象都会被标记,因为在用户线程会改变对象的一些引用。
在上面的图中,与阶段1的图进行对象,就会发现有一个对象的引用发生了变化。
2021-02-06T16:44:45.749+0800: 0.125: [CMS-concurrent-mark-start]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.001/0.001 secs
:展示该阶段持续的时间和时钟时间。这个阶段又是一个并发阶段,和应用线程并行运行,不会中断他们。前一个阶段在并行运行的时候,一些对象的引用已经发生了变化,当这些引用发生变化的时候,JVM会标记堆的这个区域为Dirty Card(包含被标记但是改变了的对象,被认为"dirty"),这就是Card Marking。
在pre-clean阶段,那些能够从dirty card对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了,如下:
另外,一些必要的清扫工作也会做,还会做一些final remark阶段需要的准备工作。
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-preclean-start]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
又一个并发阶段,不会停止应用程序线程。这个阶段尝试着去承担STW的Final Remark阶段足够多的工作。这个阶段持续的时间依赖好多的因素,由于这个阶段是重复的做相同的事情直到发生aboart的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止。
这个阶段很大程度的影响着即将来临的Final Remark的停顿,有相当一部分重要的 configuration options 和 失败的模式;
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-abortable-preclean-start]
2021-02-06T16:44:45.750+0800: 0.126: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
这个阶段是CMS中第二个并且是最后一个STW的阶段。该阶段的任务是完成标记整个年老代的所有的存活对象。由于之前的预处理是并发的,它可能跟不上应用程序改变的速度,这个时候,STW是非常需要的来完成这个严酷考验的阶段。
通常CMS尽量运行Final Remark阶段在年轻代是足够干净的时候,目的是消除紧接着的连续的几个STW阶段。
通过以上5个阶段的标记,老年代所有存活的对象已经被标记并且现在要通过Garbage Collector采用清扫的方式回收那些不能用的对象了。
2021-02-06T16:44:45.750+0800: 0.126: [GC (CMS Final Remark) [YG occupancy: 6768 K (9216 K)]2021-02-06T16:44:45.750+0800: 0.126: [Rescan (parallel) , 0.0004946 secs]2021-02-06T16:44:45.751+0800: 0.127: [weak refs processing, 0.0000192 secs]2021-02-06T16:44:45.751+0800: 0.127: [class unloading, 0.0002698 secs]2021-02-06T16:44:45.751+0800: 0.127: [scrub symbol table, 0.0001761 secs]2021-02-06T16:44:45.751+0800: 0.127: [scrub string table, 0.0000488 secs][1 CMS-remark: 8760K(10240K)] 15528K(19456K), 0.0010916 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
YG occupancy: 6768 K (9216 K)
:年轻代当前占用情况和容量。Rescan (parallel) , 0.0004946 secs
:STW,重新标记存活的对象花费的时间。weak refs processing, 0.0000192 secs
:第一个子阶段,处理弱引用。class unloading, 0.0002698 secs
:第二个子阶段,类卸载。scrub symbol table, 0.0001761 secs
:第三个子阶段,清理符号表。scrub string table, 0.0000488 secs
:第四个子阶段,清理字符串表。8760K(10240K)
:在这个阶段之后老年代占有的内存大小和老年代的容量。15528K(19456K)
:在这个阶段之后整个堆的内存大小和整个堆的容量。和应用线程同时进行,不需要STW。这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。
2021-02-06T16:44:45.751+0800: 0.127: [CMS-concurrent-sweep-start]
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-reset-start]
2021-02-06T16:44:45.752+0800: 0.128: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://morris131.blog.csdn.net/article/details/113728597
内容来源于网络,如有侵权,请联系作者删除!