天天看点

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

服务器环境8核 32G内存

问题:

在5000个连接的时候Tomcat内存基本吃满

Tomcat 压测:

连接数

内存消耗

CPU

1000

6.9G

100%

2000

12G

100%

3000

19G

100%

4000

25G

100%

4468

30G

100%

更多连接已经无法建立

检查下JVM内存使用情况,其中老年代被占用了98.4%,有大量不能释放的对象在heap。

老年代内存大小31.5G,老年代使用31G

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析
tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

JETTY压测

连接数

内存消耗

CPU

1000

1G

25%

3000

1G

25%

5000

1.1G

25%

10000

1.2G

25%

20000

1.4G

50%

内存情况老年代被使用了79.4%,老年代总大小3.5G占用0.5G

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

通过现象我们可以看到jetty占用内存非常小,而TOMCAT暂用却非常多,但是国外论坛有人Tomcat却可以压到4万的连接,是什么东西占用了这么多heap空间呢?

分析TOMCAT内存占满原因:

一,由于测试方便在本地启动程序建立了501个连接,并且用jmap生成了快照

二,我们用mat来分析下内存的情况(也可以用jhat,但是太low了很多还需要人工肉眼看和计算)

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

我们可以看到WsFrameServer占用了2.9的heap空间对象个数恰好是我们建立的连接数

三,现在我们来分析WsFrameServer对象,到底什么东西可以占用这么大

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

我们可以看到WsFrameServer对象直接引用对象的heap空间HeapCharBuffer和HeapByteBuffer非常大

我们再来看看这个大对象什么引用在引用,通过分析我们知道了原来是WsFrameServer的messageBufferText成员变量

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

下面我们来看下源代码

我们在WsFrameServer的父类中发现了这两个大对象的引用

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

那么问题来了,是什么原因导致这个对象很大的呢?我们继续看源代码什么地方用来它,特别是初始化的时候

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

我们检查到,这个地方初始化的,初始化大小是wsSession.getMaxBinaryMessageBufferSize()

和wsSession.getMaxTextMessageBufferSize(),那么问题又来了,这两个值有是从哪里来的呢?

我们查看源码

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

有一个默认值是8K,如果是8K的话内存不至于溢出,看什么地方做了赋值

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

原来是这个地方做的赋值,那么webSocketContainer又是从哪里来的呢?

我们点进去

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

这里要么是默认值,要么是什么地方做了设置我们打断点调试,而且这个默认值是8K,肯定是什么地方修改了,那么我们在set方法打断点

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

断点来了,我们通过线程栈来分析下什么地方调到这里来的,跟着往上点

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

最后我们发现是这个类ServletServerContainerFactoryBean,这个类是spring提供的,我们是在这儿用到了,原来如此,内存之所以这么大就是这里的设置导致的,这个设置的目的是让websocket可以传输更大的消息

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

其实我们看到的Tomcat的webSocketContainer是实现了javax.websocket.webSocketContainer的,很明显这个是J2EE的规范我们可以查下这个规范

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

其实这个类就是在初始化的时候可以让你设置websocket相关的一些参数

但是它设置了之后对所有session都生效了所以导致我们的连接数上不去,有什么办法可以解决吗,我们查到有另外的J2EE规范可以在运行过程中动态的修改而且是针对特定session的

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

那么问题来了,为什么jetty没有出现这个问题,如果是J2EE的规范那么我们设置这个值JETTY也应该出现这个问题,可是没有!

我们再来对jetty进行调试,发现这个配置对jetty不起作用

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

为什么不起作用呢?这又是另外的问题了~

后来通过源码分析得知JETTY和TOMCAT的实现不一样,不知道算不算BUG,个人认为是JETTY的BUG~

什么代码实现导致他们不一样呢?

Tomcat每次请求过来时在创建session时都会把这个webSocketContainer作为参数传进去所以对所有的session都生效了

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

我们来看看jetty

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

启动初始化时jetty把webSocketContainer的参数值设置给了这个过滤器,我们看看这个过滤器它的dofilter方法

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

其实在这个过滤器我们没有注册任何的访问URL,因为我们是通过spring提供的方式实现的websocket,如果我们在这个过滤器注册了访问URL那么所有过来的请求都会生效~可以继续跟后面的代码,其实就是调用的其他处理器来处理,这些处理器是在这里初始化的

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

每个url都有一个单独的处理器,这个实现是springboot提供的

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析
tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

spring给每一个websocket URL单独new 了一个处理器,jetty里面这个类是WebSocketServerFactory

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

其实jetty的处理方式是事件驱动模式设计,把所有的请求当做一个事件,不同的事件有自己对应的eventdriver来处理

重点是jetty在实例化这个对象的时候并没有把webSocketContainer所带的参数设置进去

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析
tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

这就导致了Tomcat生效jetty没有生效,其实你还会发现虽然jetty的buffer默认大小是64K,Tomcat是8K,可是jetty压测的时候CPU和内存都比Tomcat少,这是为什么呢?

原因是jetty用了对象池,不像tomcat来一次请求就new一个buffer,下面是jetty使用对象池的地方

tomcat启动占了12g_TOMCAT websocket 多连接内存泄漏与jetty对比分析

后面简单做了netty的压测10000个连接消耗内存190M,吊炸天的存在