JAVA GC日志分析

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分析各项指标的作为参考,我们也可以适当的在程序里或虚拟机参数做些调优。

猜你喜欢

转载自juejin.im/post/5cfad0da51882555f4465f06