线上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问题的排查在后面遇到类似的问题,处理起来也会清晰很多。

相关推荐
Moshow郑锴1 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
Cloud Traveler2 小时前
Kubernetes vs. OpenShift:深入比较与架构解析
架构·kubernetes·openshift
Chandler242 小时前
Go语言即时通讯系统 开发日志day1
开发语言·后端·golang
有梦想的攻城狮3 小时前
spring中的@Lazy注解详解
java·后端·spring
野犬寒鸦3 小时前
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
linux·运维·服务器·数据库·后端·github
是垚不是土4 小时前
探秘高可用负载均衡集群:企业网络架构的稳固基石
运维·服务器·网络·云原生·容器·架构·负载均衡
huohuopro4 小时前
thinkphp模板文件缺失没有报错/thinkphp无法正常访问控制器
后端·thinkphp
cainiao0806056 小时前
《Spring Boot 4.0新特性深度解析》
java·spring boot·后端
-曾牛7 小时前
Spring AI 与 Hugging Face 深度集成:打造高效文本生成应用
java·人工智能·后端·spring·搜索引擎·springai·deepseek
南玖yy7 小时前
C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
c语言·开发语言·c++·笔记·后端·游戏引擎·课程设计