OOM异常排查笔记
最近,我负责的一个SpringBoot应用在运行一段时间后会出现卡死的现象。为了解决这个问题,我进行了一系列的排查。
一、初步排查
首先,我使用lsof -i:8080
命令查看网络连接状态,发现存在大量的CLOSE_WAIT
连接。这通常意味着应用已经完成了它的数据传输并关闭了socket连接,但是对方还没有关闭连接,或者是应用层面存在某些问题导致socket资源没有被正确释放。
接着,我查看了应用的日志文件,发现报错java.lang.OutOfMemoryError: Java heap space
和java.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应用来说,这个配置显然过于宽松,很容易导致内存溢出。
为了解决这个问题,我采取了以下措施:
-
调整过期时间和数量限制:我将CaffeineCache的过期时间从1小时缩短到5分钟,并将数量限制降低到1024。这样可以确保缓存中的数据不会长时间堆积,从而减少内存占用。
-
使用弱引用缓存:我还对一些不重要且占用内存大的缓存项进行了特殊配置,使用了
.weakKeys().weakValues()
。弱引用的好处是,当没有其他强引用指向这些缓存对象时,Java垃圾回收器可以自动回收它们,从而进一步降低内存占用。
五、结果
经过上述调整,应用变得稳定了,再也没有出现过OOM异常。这次排查过程让我深刻认识到了合理配置缓存参数的重要性,也让我学会了如何有效地使用工具来分析内存问题。希望这篇笔记能对遇到类似问题的朋友们有所帮助。
版权声明:凡未经本网站书面授权,任何媒体、网站及个人不得转载、复制、重制、改动、展示或使用本网站的局部或全部的内容或服务,或在非本网站所属服务器上建立镜像。如果已转载,请自行删除。同时,我们保留进一步追究相关行为主体的法律责任的权利。我们希望与各媒体合作,签订著作权有偿使用许可合同,故转载方须书面/邮件申请,以待商榷。