CMS收集器
CMS是一款基于“标记-清除”算法的收集器,更关注系统的停顿时间。
GC主要步骤为:
1. 初始标记
2. 并发标记
3. 重新标记
4. 并发清除
如果通过GC日志来看的话,如下GC日志:
它的工作流程应当是:
只有初始标记和重新标记是:Stop The World,其实都是并发处理,不会造成应用停顿
因为CMS的并发处理,所以在并发清理阶段是和用户程序共同运行,还会有新的垃圾产生,所以CMS有个阈值,给老年代会预留一部分内存。当老年代内存占用达到CMS的这个阈值时便会触发CMS。
这个值的配置参数是:-XX:CMSInitiatingOccupancyFraction,我查的某些资料中显示,jdk1.5的这个参数默认值是68%,1.6的时候默认值是92%,我用的1.7然后我将VM参数都打印出来,查看这个值是-1,尴尬,也不知道怎么查看默认值。
接下来看下这个参数怎么触发CMS的及此时的GC情况。
测试
代码:
package com.xuxd.demo.other.cms;
import java.util.HashMap;
import java.util.Map;
/**
* Created by dong on 2019/6/5.
*/
public class CMSTest {
static int _1M = 1024 * 1024;
static Map<Integer, byte[]> persistence = new HashMap<Integer, byte[]>();
public static void main(String[] args) throws InterruptedException {
System.gc();// 先gc下
persistence.put(1, new byte[_1M]);
persistence.put(2, new byte[_1M]);
persistence.put(3, new byte[_1M]);
persistence.put(4, new byte[_1M]);
for (int i = 0; i < 7; i++) {
byte[] bytes = new byte[_1M];
}
persistence.put(5, new byte[_1M * 2]);
Thread.sleep(Integer.MAX_VALUE);
}
}
启动参数配置:
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=60
-XX:+PrintGCDetails
-Xmx20m
-XX:NewRatio=1
-XX:MaxTenuringThreshold=3
-XX:PretenureSizeThreshold=2m
上面的参数:
1. 我启用CMS收集器
2. 老年代的使用阈值是60%,达到则触发CMS
3. 打印GC详情
4. 最大堆内存20m
5. 老年代:新生代=1:1,这样的话,老年代内存为10m,eden区为8m,两个存活区各1m,则新生代为9m
6. 新生代对象经历3次gc,进入老年代
7. 达到2m的对象直接进入老年代
我用的jdk是1.7
这时候,看上面的代码,在for循环7次后,根据内存计算,此时已经经过了3次young gc,persistence这个对象占用4m多的内存,此时应当已经进入老年代,老年代的内存占用应该达到了4m多或者5m多了,此时,创建一个2m的对象,属于大对象,所以直接进入老年代,此时内存占用一定已经超过6m,达到阈值的60%,应当触发CMS了。
此时,我的控制台,也已经在不断打印GC日志了。
查看老年代内存占用,如下:
已经达到67%了。但是回收不掉,所以一直触发CMS。
查看使用jstat -gccause查看gc情况,信息如下:
已经full gc了80多次了。
注意下,我画的红方框,这个增量是2,我是每秒打印一次。
说明下:
CMS并不等于full gc。上面说过CMS有两个步骤:初始标记和重新标记,都会导致Stop The World,则每次Stop The World都会认为一次full gc,则这两次合起来包括耗时,算是一次full gc。
另外,看LGCC字段,上次GC原因都是重新标记。
上面显示当前GC原因都是No GC,我也遇到过另一种情况,分配失败导致的。
所以一定要合理设置这个阈值 ,比如示例这种情况,不停触发CMS进行GC也没用了。