Java WebSocket导致的OOM(内存溢出)问题排查

1.问题背景

线上某个工程总是会出现OOM导致系统宕机

2.排查追踪

拿到dump文件使用MAT工具分析
Java WebSocket导致的OOM(内存溢出)问题排查_第1张图片

在dominator_tree中,AbstractProtocol类占据了绝大部分堆内存
看到AbstractProtocol类猜测可能和协议有关,HTTP、TCP、SMTP、WebSocket等
Java WebSocket导致的OOM(内存溢出)问题排查_第2张图片
继续展开,可以发现WsFrameServer的类占据了大部分堆内存,定位为WebSocket协议导致的内存溢出。
再继续展开,可以发现HeapCharBuffer占据了大部分的内存。
如果项目中只有一个ws功能则就可以定义到具体业务了,如果有多个功能模块涉及ws则需要根据ws地址确认功能。
Java WebSocket导致的OOM(内存溢出)问题排查_第3张图片
通过requestUri可以查看到ws地址和请求ip
Java WebSocket导致的OOM(内存溢出)问题排查_第4张图片
通过上面排查是messageBufferText过大且链接过多导致的内存溢出,查看messageBufferText大小约20MB,则需要搞明白下面的问题:

  • 20MB是否是初始化大小?
  • 如果不是初始化大小,在哪里赋值/配置修改?
  • 如果是初始化大小,在哪里可以赋值/配置修改?

那就需要查看源码分析messageBufferText是怎么初始化大小的。

首先找到了WsFrameServer类,发现messageBufferText是WsFrameServer父类WsFrameBase的成员变量
Java WebSocket导致的OOM(内存溢出)问题排查_第5张图片
WsFrameBase类
Java WebSocket导致的OOM(内存溢出)问题排查_第6张图片
查看messageBufferText初始化:
CharBuffer.allocate静态方法创建CharBuffer的对象,其中会初始化一个大小为getMaxTextMessageBufferSize的Char类型的数组。
下面主要看wsSession.getMaxTextMessageBufferSize()方法
Java WebSocket导致的OOM(内存溢出)问题排查_第7张图片
wsSession.getMaxTextMessageBufferSize()可以看到获取的是maxTextMessageBufferSize的数值
Java WebSocket导致的OOM(内存溢出)问题排查_第8张图片
查看maxTextMessageBufferSize赋值
有一个默认值是8K,显然与想要的20MB差距比较大,继续看哪里有赋值了
Java WebSocket导致的OOM(内存溢出)问题排查_第9张图片
wsSession.setMaxTextMessageBufferSize()
Java WebSocket导致的OOM(内存溢出)问题排查_第10张图片
查看setMaxTextMessageBufferSize方法的调用
Java WebSocket导致的OOM(内存溢出)问题排查_第11张图片
在代码实现里增加了这个配置,由于char类型占两个字节,1010241024*2 约 20MB,所以这个就是原因
在这里插入图片描述
不过我们继续看另外一个调用PojoMessageHandlerWholeText
Java WebSocket导致的OOM(内存溢出)问题排查_第12张图片
往下追踪发现默认是-1,注解OnMessage的maxMessageSize字段获取值。
Java WebSocket导致的OOM(内存溢出)问题排查_第13张图片

3. 解决思路

  1. 临时解决: 现场临时增加内存分配空间
    ps:默认情况下,-Xms是物理内存的1/64,-Xmx是物理内存的1/4
  2. message初始值大小: 统计ws的maxTextMessageBufferSize大小,设置合理的数值,或考虑压缩message大小。
  3. 产品层面: 问题同步至产品,从产品层面优化。
    如:从根据可用内存对ws链接数做最大限制
  4. 服务优化:
    如:由于在项目中ws是长链接,及时释放不再使用的WebSocket链接(超时)、同一个ip和ws地址使用同一个链接
    超时功能:项目中使用nginx,可以在nginx中增加超时配置proxy_read_timeout
  5. 运维层面: 增加健康检查对于意外崩溃自动重启服务

你可能感兴趣的:(java,spring,boot,tomcat)