fullgc 在jvm中的定义是:jvm在进行老年代gc时,其进行stw(stop the word)次数。
这里我们对常见的老年代回收算法cms为例说明:
一次正常的cms过程如下:
其中,初始标记(Initial Mark) 、重新标记(Remark)两个阶段,会STW。所以这个一次正常的cms-gc就会产生两次fullgc。我们使用jstat -gcutil pid。就会看到fullgc进行了两次。
a.示例:
jvm运行参数:-Xms40m -Xmx40m -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=90? -XX:+UseCMSInitiatingOccupancyOnly
public static void main(String[] args) throws Exception {
????A[] a = new A[100000 * 15];
????for (int i = 0; i < a.length; i++) {
????????a[i % (100000 * 6)] = new A();
????}
????System.in.read();
}
static class A {int a = 0;}
gc日志:
我们运行上面的代码的时候,就会在控制台打印出上面的gc日志。通过gc日志,我们可以看到,此时应用发生了一次cms-gc。此时我们查看fullgc次数,结果如下:
可以看到,jvm统计了两次fullgc。
b.拓展:
当对系统调用 System.gc(),或者jmap -histo:live 进行对象统计时。jvm会进行一次fullgc,这次fullgc的本质是:一个serial的cms-gc(当然也会进行yong gc)。如下:(jvm参数和上面一样)
public static void main(String[] args) throws Exception {?
???A[] a = new A[100000 * 15];?
???for (int i = 0; i < a.length; i++) {?
???????a[i % (100000 * 6)] = new A();?
???}?
? ??Thread.sleep(5000);? ? //为什么要sleepSystem.gc();
????System.gc();
???System.in.read();
}
static class A {int a = 0;}
gc日志:
可以看到,这次一共进行了一次cms和一次由System .gc()触发的完整serial的fullgc。通过我们查看gc统计,如下:
一共进行了3次fullgc,其中两次是cms触发的。然后我们对应用进行 jmap -histo:live 27738
可以,看到 jmap触发了一次 serial的cms。
这里会有人疑问:这次实验的代码,为什么要加一个 Thread.sleep(5000);? 这里感兴趣的小伙伴,可以跑一下代码,把这个时间改成其他值,会发生什么。
c.探索
其实笔者在上述实验过程中,构造例子花了很长时间,比如下面的一个运行示例(jvm参数和上面一致):
public static void main(String[] args) throws Exception {
????Map map = new HashMap();
????for (int i = 0; i < 27; i++) {
????????byte[] b = new byte[1024*1024];
????????map.put(i,b);
????}
System.in.read();
}
可以看到,olde区没有打到触发的gc阈值,但是我们程序一直在进行cms-gc,而且一直不停止。主要原因是:old区进行gc的时候,不仅仅是依靠 old区当前内存使用量,还会有其他因素触发old区进行gc。具体原理可以参考以下两片文章:
http://08643.cn/p/a322309b1d90
https://blog.csdn.net/foolishandstupid/article/details/77430875
https://blog.csdn.net/zl1zl2zl3/article/details/89087327
推荐两篇jvm的文章: