最近,我负责的一个SpringBoot应用在运行一段时间后会出现卡死的现象。为了解决这个问题,我进行了一系列的排查。

一、初步排查

首先,我使用lsof -i:8080命令查看网络连接状态,发现存在大量的CLOSE_WAIT连接。这通常意味着应用已经完成了它的数据传输并关闭了socket连接,但是对方还没有关闭连接,或者是应用层面存在某些问题导致socket资源没有被正确释放。

接着,我查看了应用的日志文件,发现报错java.lang.OutOfMemoryError: Java heap spacejava.lang.OutOfMemoryError: GC overhead limit exceeded。这两个错误都指向了Java堆内存溢出的问题。

二、获取OOM现场

由于应用已经重启过,我无法直接获取到OOM时的现场数据。为了能够在下次OOM发生时捕获到现场数据,我给JVM增加了启动参数-XX:+HeapDumpOnOutOfMemoryError。这个参数会在OOM发生时自动生成堆转储文件(hprof)。

如果你的OOM现场还在,可以使用使用 jmap 工具生成堆转储文件,命令如jmap -dump:format=b,file=<filename>.hprof <pid>

三、分析hprof文件

在OOM再次发生后,我得到了一个hprof文件。通过IntelliJ IDEA打开并分析这个文件,我发现org.springframework.cache.caffeine.CaffeineCache这个对象占用了大量的内存,其保留集大小超过了300M。

四、定位问题与解决方案

查看相关代码后,我发现CaffeineCache的过期时间被设置为了1小时,数量限制为1万。对于一个Xmx设置为400M的Java应用来说,这个配置显然过于宽松,很容易导致内存溢出。

为了解决这个问题,我采取了以下措施:

  1. 调整过期时间和数量限制:我将CaffeineCache的过期时间从1小时缩短到5分钟,并将数量限制降低到1024。这样可以确保缓存中的数据不会长时间堆积,从而减少内存占用。

  2. 使用弱引用缓存:我还对一些不重要且占用内存大的缓存项进行了特殊配置,使用了.weakKeys().weakValues()。弱引用的好处是,当没有其他强引用指向这些缓存对象时,Java垃圾回收器可以自动回收它们,从而进一步降低内存占用。

五、结果

经过上述调整,应用变得稳定了,再也没有出现过OOM异常。这次排查过程让我深刻认识到了合理配置缓存参数的重要性,也让我学会了如何有效地使用工具来分析内存问题。希望这篇笔记能对遇到类似问题的朋友们有所帮助。