GC环境模拟
首先我们给出如下代码用来触发GC
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
try {
// 申请512kb
byte[] temp = new byte[1024 * 512];
Thread.sleep(new Random().nextInt(100)); // 随机睡眠100毫秒以内
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}, 100, 100, TimeUnit.MILLISECONDS); // 延迟100ms,间隔100ms
}
复制代码
程序逻辑:每隔100ms启动一个线程申请空间然后随机休眠一段时间。之所以随机睡眠是为了避免对象朝生夕灭,更好的触发Young GC 和 Full GC。
虚拟机参数解释
启动Java进程:java -Xms200m -Xmx200m -Xmn100m -verbose:gc -XX:+PrintGCDetails -Xloggc:./gc.log -XX:+PrintGCDateStamps -jar demo-0.0.1-SNAPSHOT.jar
-Xms200m -Xmx200m 最小/最大堆内存 200M
-Xmn100m 年轻代内存 100M
-verbose:gc 开启GC日志
-XX:+PrintGCDetails -Xloggc:./gc.log -XX:+PrintGCDateStamps 将GC日志详情输入到gc.log中
jmap分析
jcmd 获取我们Java进程的Id:6264
jmap -heap 6264查看堆信息
第一次查看,我们发现 Eden区是92M,S0、S1是4M
第二次查看, Eden区是99M,S0、S1是0.5M
Eden区与Survivor区的比例在动态的变化,并不是默认的8:1:1。
原来我们使用默认的垃圾收集器Parallel Scavenge+Parallel Old组合,而该收集器下-XX:+UseAdaptiveSizePolicy是默认开启的,即Eden区与Survivor区比例根据GC情况会自适应变化。
我们加上参数,关闭年轻代自适应,年轻代比例设置为8:1:1
-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=8
重启虚拟机查看jmap
年轻代
-
Eden区80M 已使用80M,当前使用率64.8%
-
S0区10M 已使用0.15M,使用率1.5%
-
S1区10M 使用率为空
老年代
- 100M 已使用18.9M,使用率18.9%
GC日志内容分析
查看我们输出的GC日志gc.log,选取其中两段
Young GC
[GC (Allocation Failure) [PSYoungGen: 66181K->25600K(70144K)] 155856K->121956K(172544K), 0.0073212 secs]
解释:
-
年轻代GC:[GC前年轻代64.6M->GC后25M(年轻代总大小68.5M)]GC前堆152.2M->GC后堆119.1M(堆总大小168.5M),用时]
-
其中年轻代总大小是68.5M而不是100M,这里我理解是年轻代最大申请到68.5M
-
100M*80%=80M 是Eden区大小
-
80M*80% = 64M Eden区默认占用超过8成即64M就会触发YoungGC
Full GC
[Full GC (Ergonomics) [PSYoungGen: 25600K->0K(70144K)] [ParOldGen: 96355K->39438K(102400K)] 121956K->39438K(172544K), [Metaspace: 16935K->16933K(1064960K)], 0.0366222 secs]
解释:
-
[GC前年轻代25M->GC后年轻代0M(年轻代总大小68.5)][GC前老年代94M->GC后老年代38.5(老年代总大小100M)]GC前堆119.1M->GC后堆38.5(堆总大小168.5),[元数据区:GC前16.5,GC后16.5(元数据区总大小1040M)],用时]
-
由GC前年轻代25M,老年代94M可以推测出此次FullGC原因是年轻代晋升老年代空间不足导致
利用可视化工具分析
这里我们利用gceasy分析一下
(1)统计年轻代、老年代、元数据区最大可用空间以及峰值,这里元数据区大小在我们的虚拟机参数没有配置,所以取的是默认值
(2)吞吐量、GC平均延迟、最大延迟以及延迟区间的统计
(3)堆所用大小的实时分析,红色位置是发生了FullGC使得堆总量直线下降
放大一下查看
(4)GC空间总量和时间的统计
(5)各类GC时间、GC次数、GC总量等指标
总结
GC日志分析可以帮助我们宏观的监控GC运行情况。一方面如果频繁的FullGC会有严重的性能问题(STW),另一方面过于频繁的GC,即GC占用系统正常运行的比重过多,吞吐量低,则是一定程度上的性能资源浪费。若系统存在性能问题,根据GC分析各项指标的作为参考,我们也可以适当的在程序里或虚拟机参数做些调优。