线上FullGC问题如何排查 - Java版

Java线上FullGC问题如何排查

在线上环境,我们可能会经常遇到程序执行缓慢,甚至频繁出现卡顿的情况。有时候通过top命令查看进程,甚至出现cpu运行接近100%

的情况。如果线上我们接入了监控告警,可能此时就会收到大量的GC告警。这些现象的产生,很可能是我们的程序在频繁的发生FullGC

频繁的发生FullGC是程序一个隐藏的非常严重的问题,严重影响到服务的性能,对于FullGC问题的定位进行一个思路分析

GC优化指标

满足以下两个条件一般认为GC就是正常的,否则就要进行优化了

  • Minor GC执行时间不到50ms,Minor GC执行不频繁,约10秒一次;
  • Full GC执行时间不到1s,Full GC执行频率不算频繁,约10分钟一次;

案例分析

假设我们有以下SpringBoot的web工程,其controller代码如下:

java 复制代码
@RestController
public class GcController {
    static class StaticGcObject {

    }
    @GetMapping("/gcDemo")
    public String ooM() throws InterruptedException {
        List<StaticGcObject> list = new ArrayList<>();
        int i = 0;
        Thread.sleep(10000);
        while(true) {
            list.add(new StaticGcObject());
            i++;
            System.out.println(i);
            System.out.println(list.size());
        }
    }
}

这个controller的逻辑是有一个list对象,然后触发接口后,会不断的往list集合中添加StaticGcObject对象,导致list越来越大

1.场景模拟

在IDE里面配置上jvm和gc相关的参数

参数命令如下:

ini 复制代码
-Xmx60m
-Xms60m
-XX:SurvivorRatio=8
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/Users/lisirui/Documents/gcDemo/gc-demo.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/lisirui/Documents/gcDemo/fullGC-heap-dump.hprof

设置运行内存大小为60M,根据默认的分配比例(1:2)可以得到,年轻代 : 老年代 = 20M : 40M,年轻代中根据 XX:SurvivorRatio=8

可以得到eden : s0 : s1 = 16M : 2M : 2M。然后通过-Xlog:gc选项将GC日志生成到/Users/lisirui/Documents/gcDemo/gc-demo.log文件

2.启动项目

在浏览器访问http://localhost:8100/gcDemo,可以看到一直处于等待状态,过一会,会出现错误页面

在程序控制台可以看到堆内存溢出的报错,是正常情况,不影响我们分析GC的问题

3.收集GC日志

到刚刚在VM Option设置jvm参数指定的gc日志路径下,可以看到生成了的GC日志文件gc-demo.log

4.分析GC日志

打开gc-demo.log文件,确实在发生频繁的FullGC,可以初步判断,程序反应慢以及最后的报错跟GC有关

分析gc-demo.log日志文件的时候可以直接打开手动分析,查找GC关键字,初略分析GC的频率和耗时。

也可以用GCViewer工具打开GC日志文件来分析,后者对于一些指标的观察可能更直观,比如GC过程中的总耗时,平均暂停间隔时间等

5.监控JVM GC情况

打开VisualVM工具,打开控制台输入jvisualvm即可

在打开的左侧可以看到运行的java进程,找到我们当前测试的进程,然后右边的Visual GC一开始可能是没有的,需要到插件市场下载

可以看到图中,Eden进行了47此GC,虽然时间短,但是能说明一个问题,新生代堆内存分配的空间小,导致Eden区频繁GC

从Survivor区可以看出,基本没有GC,说明这些都是大对象,直接进入到了Old老年代

到后面会发现,Old老年代在进行频繁的GC,次数达到140+,因为Eden区的对象要进入Old老年代,但是Old放不下了,就频繁GC

所以我们需要加大新生代和老年代的内存大小,同时减少大对象的产生

6.JVM参数调整

从上面的分析来看,GC发生很频繁,所以我们适当的调大JVM的内存大小,同时开启Heap Dump文件,JVM参数修改如下:

ini 复制代码
-Xmx512m
-Xms512m
-XX:SurvivorRatio=8
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/Users/lisirui/Documents/gcDemo/gc-demo.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/lisirui/Documents/gcDemo/fullGC-heap-dump.hprof

然后启动程序,在浏览器访问http://lovalhost:8100/geDemo,可以看到前端页面一直处于等待状态

后台程序一直在运行中,过了一段时间报错,查看后台程序的运行情况,报了堆内存溢出的Error

但是这次的等待时间相比上一次明显要更久,再次打开VisualVm工具,观察GC情况

在相同的时间内,60M和512M的内存大小分配明显的GC次数会少很多,但是到后面仍然发现Old老年代始终会满

7.分析Heap Dump文件

通过加大JVM的内存,一定程度上优化了GC频繁的问题,但是Old老年代仍然存在问题,在运行一段时间后,程序依旧报堆内存溢出Error

这说明我们的程序代码是又问题的,所以进一步分析Heap Dump文件。

回顾我们刚刚指定的JVM参数,发现在目录下生成了fullGC-heap-dump.hprof文件

ini 复制代码
-XX:HeapDumpPath=/Users/lisirui/Documents/gcDemo/fullGC-heap-dump.hprof

用VisualVM工具打开fullGC-heap-dump.hprof文件

发现在Controller类中的StaticGcObject类型占用了大量的内存,而且这个类型的对象数量非常多,问题就出在这。

8.代码审查

通过上一步的heap dump文件分析,我们确定了在代码中存在占据大量内存的对象类型StaticGcObject,去代码中定位

代码发现存在一个StaticGcObject类型的列表对象list,并且在这个controller中,通过访问触发之后,会在一个死循环中不停的创建新的

StaticGcObject对象越来越多,list越来越大,由于没有做限制,最终肯定会导致堆内存溢出

9.代码优化

简单优化,对于无上限的list添加做一个限制,当达到指定大小之后,就不允许添加,这个根据实际场景找出代码问题去优化即可

java 复制代码
@RestController
public class GcController {

    private static final int MAX_SIZE = 100;

    static class StaticGcObject {

    }
    @GetMapping("/gcDemo")
    public String ooM() throws InterruptedException {
        List<StaticGcObject> list = new ArrayList<>();
        int i = 0;
        Thread.sleep(10000);
        while(true) {
            if(list.size() > MAX_SIZE) {
                return "OOM Test Over";
            }
            list.add(new StaticGcObject());
            i++;
            System.out.println(i);
            System.out.println(list.size());
        }
    }
}

10.方案验证

优化完代码再次启动程序,访问http://localhost:8100/gcDemo,返回结果如下

再次打开VisualVM工具观察,查看一下GC情况

小结

首先是要识别出什么场景下可能发生线上的频繁FullGC,一般程序发生频繁FullGC都会造成程序运行缓慢,出现卡顿的情况。

一旦出现这种情况,就要考虑FullGC的问题了。识别出现象之后,就要开始收集堆内存日志,分析日志,以及利用JVM内存分析工具

查看GC情况,如果确实是发生了频繁的FullGC,通过JVM内存分析工具比如VisualVM都能直观的看到GC发生次数,频率,以及耗时

然后针对性的进行处理,比如调整JVM参数,最后如果效果还是不理想,一般还需要继续分析heap dump文件来定位代码问题

通过梳理排查FullGC问题的思路,对FullGC问题的排查在后面遇到类似的问题,处理起来也会清晰很多。

相关推荐
gyx_这个杀手不太冷静10 分钟前
Vue3 响应式系统探秘:watch 如何成为你的数据侦探
前端·vue.js·架构
风象南20 分钟前
SpringBoot实现简易直播
java·spring boot·后端
这里有鱼汤28 分钟前
有人说10日低点买入法,赢率高达95%?我不信,于是亲自回测了下…
后端·python
eternal__day1 小时前
微服务架构下的服务注册与发现:Eureka 深度解析
java·spring cloud·微服务·eureka·架构·maven
武子康1 小时前
Java-39 深入浅出 Spring - AOP切面增强 核心概念 通知类型 XML+注解方式 附代码
xml·java·大数据·开发语言·后端·spring
米粉03051 小时前
SpringBoot核心注解详解及3.0与2.0版本深度对比
java·spring boot·后端
Wang's Blog2 小时前
Monorepo架构: Nx Cloud 扩展能力与缓存加速
缓存·架构
一只帆記2 小时前
SpringBoot EhCache 缓存
spring boot·后端·缓存
yuren_xia5 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
JohnYan8 小时前
Bun技术评估 - 04 HTTP Client
javascript·后端·bun