目录
一、什么是CMS垃圾收集器
虽然HotSpot虚拟机已经在jdk14中移除了CMS垃圾收集的参数,但是考虑到还有很多开发是基于jdk8开发的,所以还是有必要了解一下CMS垃圾收集器的。CMS(Concurrent Mark Sweep)收集器是一种基于标记清除算法,追求最短停顿时间的真正意义上的第一款并发垃圾收集器。
二、CMS垃圾收集的过程
从上面的图上可以看出,CMS垃圾收集器需要经过初始标记、并发标记、重新标记、并发清除四个阶段。
初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,这个过程需要STW。
并发标记就是从GC Roots开始遍历整个对象引用的过程,这个阶段时间较长,但是适合用户线程并发执行的不需要停顿用户线程,这个过程不需要STW。
重新标记阶段是为了修正并发标记期间,因为用户线程执行而导致标记产生变动(错标、漏标)哪一部分对象(具体的内容可以参考JVM垃圾回收——三色标记法_熟透的蜗牛的博客-CSDN博客),这个阶段停顿时间通常比初始标记时间长,但也远比并发标记时间短,这个过程也需要进行STW。
并发清除阶段是清理删除标记阶段已被判处死亡的对象,由于基于清除算法,不需要移动存活对象,这个阶段可以和用户线程并发执行。
三、CMS收集器的不足
仔细想一下CMS虽然是一个和用户线程并发执行的收集器,不需要停顿用户线程或许你会认为这个效果还是挺好的,你这样想那就格局有点小了,虽然在并发阶段不会导致用户线程的停顿,但却占用了CPU,导致用户的应用变慢,导致应用的吞吐量下降,这就是CMS收集器的第一个不足之处,对处理器资源非常敏感。CMS默认启动的回收线程数为(处理器(CPU)核心数+3)/4 ,也就是说如果CPU的核心数在四个或者四个以上,并发回收的垃圾收集线程占用不少于1/4的CPU资源。假设我的服务器是2核的,那么(2+3)/4=1,需要一半的核心数来处理垃圾收集,那这样是个不能接受的问题。
在CMS并发标记和并发清除阶段,用户线程还是活跃的,自然也会产生新的垃圾对象,那么这些垃圾对象就只能等到下一次垃圾回收的时候才能进行清除,这一部分我们叫做“浮动垃圾”。同样因为并发收集垃圾的原因,CMS收集器需要预留足够的内存空间来存储新的对象,所以CMS收集器并不能像其他收集器一样等到老年代几乎满了之后才会回收垃圾。在jdk5默认当老年代使用达到68%的空间后就会进行垃圾清除,而在jdk6之后这个默认值设定为92%。这个参数可以通过JVM参数-XX:CMSInitiatingOccupancyFraction参数来设定。如果这个值设置的过小垃圾回收就相对频繁,如果设置的过大就会产生“并发失败”(Concurrent Mode Failure),如果出现并发失败,那么JVM就不得不暂停用户线程,临时启用Serial Old收集器来重新进行老年代的回收,这样整个垃圾回收过程就会停顿很长的时间。因此这个值需要在实际生产过程中根据实际情况配置,这是CMS收集器的二一个不足之处。
三一个不足之处就是CMS是基于标记-清除的算法实现的垃圾收集器,所以CMS垃圾收集会产生大量的空间碎片,由于大量的空间碎片的产生,当分配大对象找不到足够的空间时,就不得不提前进行一次Full GC。为了解决这个问题JVM提供了参数-XX:+UseCMSCompactAtFullCollection参数,这是一个开关参数,默认是开启的,在jdk9之后废弃。
四、CMS收集器的参数配置
测试代码
package com.wssnail.test;
import java.util.ArrayList;
import java.util.List;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 测试CMS垃圾收集器
* @date 2022/11/9 21:56
*/
public class TestCMSGc {
private static String[] strArr = new String[]{"中国人民万岁", "梅西好样的,梅西好样的梅西好样的梅西好样的梅西好样的梅西好样的梅西好样的梅西好样的", "我爱看世界杯,我爱看世界杯我爱看世界杯我爱看世界杯我爱看世界杯我爱看世界杯我爱看世界杯我爱看世界杯我爱看世界杯"};
public static void main(String[] args) {
List<String[]> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(strArr);
}
while (true) {
}
}
}
Jvm参数配置
-Xmx20M
-Xms20M
-Xmn6M
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=60
-XX:+UseCMSCompactAtFullCollection
-XX:+PrintCommandLineFlags
打印出明细如下
-XX:CMSInitiatingOccupancyFraction=60
-XX:InitialHeapSize=20971520
-XX:MaxHeapSize=20971520
-XX:MaxNewSize=6291456
-XX:MaxTenuringThreshold=6
-XX:NewSize=6291456
-XX:OldPLABSize=16
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:+UseCMSCompactAtFullCollection
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:+UseConcMarkSweepGC
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParNewGC
-XX:+UseParNewGC,-XX:MaxTenuringThreshold=6 ,可以看出 如果配置了CMS垃圾收集器,默认新生代使用的是ParNewGC收集器,并且复制的次数为6次时会进入老年代,如下两张图
第六次复制
第七次复制可以看到老年代由2.030M到2.160M
其他的具体细节,请感兴趣的朋友自己动手实验一下,观察一下CMS垃圾收集的过程。