不知道从哪一天开始,项目每隔一段时间,就会出现一次OOM问题,具体的表现就是,刚开始,用户请求变慢,隔几分钟后,所有的请求都没有响应,最终服务出现FULL GC问题。
日志大概是这样的:
最开始,以为是项目中缓存或者是数据库配置的导致的问题,但是改了之后依然不行。
由于这个问题是偶现的,每次出现问题,为了项目正常使用,基本上都是直接重启来解决。
终于有一天,服务又崩了,于是下定决心要把这个问题找出来。
解决这种问题,离不开JVM的相关知识,以及java提供的一些工具。
分析问题
首先,使用 jstat 命令,分析jvm的GC情况,
jstat -gcutil pid 1000 (pid是通过top命令找到的java线程的pid)
可以发现,O(老年代占用了大量的空间) 同时FGC频繁回收,导致的问题。
接下来,就是通过 jmap命令,分析下内存的使用情况了
jmap -histo:live pid | more (pid是通过top命令找到的java线程的pid)
我们的应用,启动配置参数是这样的 -Xms2560m -Xmx2560m
但是可以看到,这其中 一个[B的class ,就占用了1.6G的内存,这个[B是啥呢,他是JAVA中的byte数组。
为啥byte数组会占用了这么多的内存,我是满脑子的问号。
没办法,只能把内存文件dump出来分析下了。
使用命令
jmap -dump:format=b,file=a.prof pid (pid是通过top命令找到的java线程的pid)
最终我们得到了一个 a.prof的文件,这个文件通常比较大,像我们的。足足2个G,毕竟项目分配了2个G,GC OOM导致都占用完了。
然后使用gzip 命令压缩下,
gzip -q a.prof
压缩后,文件只有50M不到,这样就能方便下载下来分析了。
下载下来后,通过 MAT工具进行分析,关于这个工具,可以去网上找教程进行下载安装。
打开软件后,通过 File- Open Heap Dump 打开我们上面的dump文件。
然后点击 Dominator Tree ,看下具体的占用
发现这其中,并没有像网上那种,有一个单独的对象占用了很大的内存,而是有很多个byte数组平均占用了内存,每个都占用了大概10M 的内存,然后数量一多,就导致了OOM,通过className,可以发现,是一个http请求路径。
这个路径,在项目里面对的作用,是给三方渠道作为下载链接使用的,那为啥会这么多这个请求呢。
通过HTTP请求日志发现,每次问题发生时,都会有大量的请求调用这个接口,高峰大概每秒50+。
这个请求量,说大不大,说小也不小。正常单机tomcat对付这个,应该是问题不大的。但是为啥还出现这个问题呢。
问题出现的原因,是每个请求占用了10M的内存导致的,最终我在配置文件里,看到了这么一行
max-http-header-size: 10000000
这个配置的作用,是配置请求头最大值,但是这就导致,一个请求进来,tomcat就会申请10M的内存空间,当大量请求同时间进来,内存来不及释放,最终导致了这个内存泄漏的问题。
解决办法很简单,把这个值改小就行,正常项目一般100K就足够了。
终于把这个困扰了几个月的问题解决了!!!
关于 max-http-header-size的设置可以参考 https://www.baeldung.com/spring-boot-max-http-header-size
网上其他人碰到的这个问题 https://blog.csdn.net/xiaoming120915/article/details/120472272