CMS收集器的gc情况分析

176 阅读3分钟

这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战

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也没用了。