websocket引起的内存泄漏问题排查
1 问题描述
项目运行一天后出现了java.lang.OutOfMemoryError: GC overhead limit exceeded
的错误,造成系统宕机。这说明给JVM分配的内存已经耗尽,不足以支撑垃圾回收进行内存回收工作,意味着程序占用的内存随着时间大小提升,最终耗尽。
2 问题分析与排查
2.1 宏观分析
从字面意思来看,GC(garbage collection)所需的内存不足,空闲内存与GC之间平衡的一个限制被打破,当经过几次GC之后,只有少于2%的内存被释放,也就是很少的空闲内存,可能会再次被快速填充,这样就会触发再一次的Full GC。如此往复,CPU大部分的时间在做GC操作(Full GC所需的时间很长),具体的业务操作也因为内存不足而无法运转,整个应用程序就崩溃了。虽然和C++不同的是,JAVA拥有独特的垃圾回收机制,程序员可以较少的担忧资源回收的问题,但是这种机制并不可能100%防止内存泄漏,一些难以察觉的代码实现或是配置都可能会造成这种机制失效,例如JAVA拥有多种不通强度的引用关系,例如强引用(strong reference),弱引用(weak reference),软引用(soft reference)和虚引用(phantom reference)。
引用类型 | 回收时间 | 生存时间 |
---|---|---|
强引用 | 不回收 | JVM停止 |
软引用 | 内存不足时 | 内存不足时 |
弱引用 | GC运行时 | GC运行后 |
虚引用 | 未知 | 未知 |
可以看到,JVM在设计中尽可能避免了内存溢出的问题,主要引用类型为软引用和强引用,在内存不足时,软引用将会被释放。
2.2 查看gc回收日志
首先先找到项目对应的pid,可以使用ps -ef | grep java
获取,随后在启动项目的命令中添加以下参数
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log demo.jar
其中,+printGCDetails
和+PrintGCDateStamps
分别确保了输出gc细节和时间戳,这有助于我们发现问题,-Xloggc:gc.log
则是指定了输出的日志文件名称。
可以看到,每条信息的组成为时间戳
+执行的操作
+不同区的使用情况
+回收所需时间
,以上图中第一条日志为例,在2023-03-27T22:25:51
发生了一次GC(System.gc())
,PSYoungGen
区域在GC前使用了412645K
,GC后的大小为61872K
,该区域的总大小为1949696K
,而对于Full GC,还会显示ParOldGen
区域的信息。从日志中我们可以至少获得以下信息:
- full gc所需的时间比一般gc更长(0.09秒对0.02秒)
- full gc的频率非常高(1分钟一次,项目实际生产情况为1秒一次)
- 出现了allocation failure(内存分配失败引起,JVM会执行Young GC回收年轻代的内存,如果完成后仍需内存回收,则会执行Full GC对年轻代和老年代进行回收)
- 释放后的老年代大小正在缓慢增加(需观察更长时间的日志)
从上面信息中,可以推断出程序一定有设计不足的地方,显然出现了严重的内存泄漏,而之所以还没有崩溃,是因为不断触发Full GC释放出了足够的内存,对用户而言此时可能会感知到的只是略微卡顿和较慢的响应。而最后一点,老年代的大小随着时间正在逐渐变大,这无疑是一颗定时炸弹,可以预见的是,当老年代大小逐渐被耗尽,将会加速程序的崩溃。
2.3 寻找导致内存泄漏的根源
现在已经可以确定发生了内存泄漏,但是如何寻找导致内存泄漏的原因成为了下一个难题。好在已经有了较为成熟的工具帮助我们,它就是MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,但也可以单独使用,并且支持Linux,Mac,Windows系统。它的下载连接是链接: 官网。需要注意的是,最新版本需要JDK11支持,推荐下载1.7.0版本,只需要JDK8即可。
先利用jmap
获取一个堆内存快照,jmap -dump:format=b,file=myjmapfile.hprof 进程id
,随后用MAT打开即可查看堆内存占用情况。
在dominator_tree中,可以发现名为WsFrameServer
的类占据了绝大部分堆内存,继续展开,可以发现java.nio.HeapCharBuffer
和java.nio.HeapByteBuffer
占据了绝大部分的内存。
其实到这里,我们已经可以推断出问题了,那就是Buffer缓冲区太大,而它又发生在websocket当中,所以我们只需要找到websocket初始化缓冲区大小的地方,即可解答为什么缓冲区会如此之大的问题。
在Websocket配置类中,果然发现了问题所在
此处将textBufferSize
和bianryBufferSize
设置成了50MB,因此很容易导致内存不足以回收。
3 解决办法
3.1 临时办法-减小buffer size
将textBufferSize
和bianryBufferSize
设置相对较小,同时满足业务要求。然而,这种做法却并不能绝对保证内存溢出问题的解决,因为我们仍然没有确切的找到哪些对象因为哪些原因没有被回收导致的内存溢出。但这种做法应该能够延缓系统崩溃的时间。文章来源:https://www.toymoban.com/news/detail-544204.html
3.2 更换Tomcat版本
也有人提到SpringBoot和Tomcat有一些微妙的关系,例如链接: link。文章来源地址https://www.toymoban.com/news/detail-544204.html
到了这里,关于[JAVA]websocket引起的内存泄漏问题排查的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!