测试代码
代码的jvm参数:-verbose:gc -Xms10m、-Xmx10m、-XX:+UseG1GC、-XX:+PrintGCDetails、-XX:+PrintGCDateStamps、-XX:+PrintGCTimeStamps、-XX:MaxGCPauseMillis=200m;
重要参数详解:
-XX:+UseG1GC:由于是jdk8所以需要指定使用G1收集器;
-XX:+PrintGCDateStamps:表示打印GC日志的系统时间;
-XX:+PrintGCTimeStamps:打印JVM启动的时候的相对时间;
-XX:MaxGCPauseMillis=200m:是G1中最重要的参数之一,表示期望GC过程中停顿的时间,这里表示设置200ms
G1收集器只用指定最大最小堆,不用指定新生代大小,由G1收集器自动分配更好!
代码如下图:
每个数组是1M,数组也会分布到堆中,其中createOneM()方法创造的1M数组由于是方法,在方法执行完成后,下次GC的时候这个数组会被回收,而其他数组不会被回收。
GC日志详解
这段代码的GC日志较长,所以进行分段分析。
第一段GC日志如下图:
第一块红框内参数详解:
◆2020-04-16T15:59:17.614+0800: 0.158:系统时间,0.158表示想到JVM启动时间;
◆GC:GC标识;
◆pause:表示暂停了用户线程;
◆G1 Humongous Allocation:括号表示触发GC的原因,G1中有Humongous区域,对象占用内存大于Region内存一半都会存入Humongous区,这里Region内存大小是1M(最后一步能看到)。
◆young:表明是Young GC;
◆initial-mark:初始标记;
第二块红框就是Young GC过程,主要过程如下:
◆[Parallel Time: 0.7 ms, GC Workers: 4]:表示并行用时0.7ms,4个并行线程;
详细Young GC 过程:Ext Root Scanning(根扫描)、Update RS(更新RS)、Scan RS(处理RS)、Code Root Scanning(code root扫描,code root是JIT编译后的代码里引用了heap中的对象)、Object Copy(对象拷贝)、GC Worker Other(GC线程其他花费时间)、GC Worker Total(4条GC线程总时间)、GC Worker End(最快、最慢GC线程结束时间,是相对启动时间的);
第三个红框内容详解:
◆Code Root Fixup、Code Root Purge:修复和清除Code Root;
◆Clear CT:清除card tables 中的dirty card;
◆Other:其他处理,比如选择CSet、处理引用队列(YGC第五阶段)、释放CSet;
最后一个红框:
YGC完成最后展示各个空间GC的内存变化,首先Eden区内存被清空,同时Eden区原来大小是4096K,GC后变成了3072K,说明G1对Eden进行了调整。同时Survivors由0变成了1024K,总共堆使用由4352到1787,说明清理了。
全局并发标记
在YGC完后后紧接着就进行了全局并发标记过程,日志如下图:
主要分如下几个步骤:concurrent-root-region-scan-start(根区域扫描)、concurrent-mark-start(并发标记)、remark(重新标记)、cleanup(清除垃圾),其中initial-mark(初始标记)在YGC开始的时间已经执行完成。
再次GC
从上面那张图看到清理完成后有进行了GC,详情如下图:
这次GC有点不同的是在young后面加了一个to-space exhausted(空间耗尽)标识,还有一个是在other中多出了一个步骤Evacuation Failure(晋升失败),并且他花费了0.6ms,是这个过程花费最长的而且是长很多的。
我们先看看GC的结果,Eden区使用由1024变成了0,Survivor也是由1024K变成了0,堆总体使用基本无变化,说明Eden区的对象并没有进入Survivor而是直接进入了老年代,这里就对应了Evacuation Failure。
最终结果
这个整个GC还有好几次,不过过程都差不多,直接看最后结果,日志如下图:
经历过几次GC后,可以看到最后一次YGC几乎没有回收任何内存,所以最后不得不进行了一次Full GC。最后看堆分配总共10240K,使用4714K。region size 1024K表示Region大小是1024K,1 young (1024K)表示1个young Region 占用内存1024K,,0 survivors (0K)表示0个survivors。
总结
学习了G1的理论还要真正的实例来证明才能真正吸收,当然现在线上真实环境很少能接触到,这里通过一个简单的例子来实现了G1的几个过程,验证了之前的理论学习,尤其其中几个点:
1、新生代的不断调整,可以看出G1在GC过程中会不断的调整Eden的大小,同时发现日志中并没有体现survivor的大小,只显示了使用大小,说明这个也是动态变化的,使用了多少就算多少。
2、Evacuation Failure,发现占用的时间特别多,直译过来是晋升失败,类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集。
3、G1最终的GC还是Full GC,当正常的GC不满足需求的时候就会触发Full GC。
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!
领取专属 10元无门槛券
私享最新 技术干货