服务器环境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
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
通过现象我们可以看到jetty占用内存非常小,而TOMCAT暂用却非常多,但是国外论坛有人Tomcat却可以压到4万的连接,是什么东西占用了这么多heap空间呢?
分析TOMCAT内存占满原因:
一,由于测试方便在本地启动程序建立了501个连接,并且用jmap生成了快照
二,我们用mat来分析下内存的情况(也可以用jhat,但是太low了很多还需要人工肉眼看和计算)
我们可以看到WsFrameServer占用了2.9的heap空间对象个数恰好是我们建立的连接数
三,现在我们来分析WsFrameServer对象,到底什么东西可以占用这么大
我们可以看到WsFrameServer对象直接引用对象的heap空间HeapCharBuffer和HeapByteBuffer非常大
我们再来看看这个大对象什么引用在引用,通过分析我们知道了原来是WsFrameServer的messageBufferText成员变量
下面我们来看下源代码
我们在WsFrameServer的父类中发现了这两个大对象的引用
那么问题来了,是什么原因导致这个对象很大的呢?我们继续看源代码什么地方用来它,特别是初始化的时候
我们检查到,这个地方初始化的,初始化大小是wsSession.getMaxBinaryMessageBufferSize()
和wsSession.getMaxTextMessageBufferSize(),那么问题又来了,这两个值有是从哪里来的呢?
我们查看源码
有一个默认值是8K,如果是8K的话内存不至于溢出,看什么地方做了赋值
原来是这个地方做的赋值,那么webSocketContainer又是从哪里来的呢?
我们点进去
这里要么是默认值,要么是什么地方做了设置我们打断点调试,而且这个默认值是8K,肯定是什么地方修改了,那么我们在set方法打断点
断点来了,我们通过线程栈来分析下什么地方调到这里来的,跟着往上点
最后我们发现是这个类ServletServerContainerFactoryBean,这个类是spring提供的,我们是在这儿用到了,原来如此,内存之所以这么大就是这里的设置导致的,这个设置的目的是让websocket可以传输更大的消息
其实我们看到的Tomcat的webSocketContainer是实现了javax.websocket.webSocketContainer的,很明显这个是J2EE的规范我们可以查下这个规范
https://docs.oracle.com/javaee/7/api/javax/websocket/WebSocketContainer.html
其实这个类就是在初始化的时候可以让你设置websocket相关的一些参数
但是它设置了之后对所有session都生效了所以导致我们的连接数上不去,有什么办法可以解决吗,我们查到有另外的J2EE规范可以在运行过程中动态的修改而且是针对特定session的
那么问题来了,为什么jetty没有出现这个问题,如果是J2EE的规范那么我们设置这个值JETTY也应该出现这个问题,可是没有!
我们再来对jetty进行调试,发现这个配置对jetty不起作用
为什么不起作用呢?这又是另外的问题了~
后来通过源码分析得知JETTY和TOMCAT的实现不一样,不知道算不算BUG,个人认为是JETTY的BUG~
什么代码实现导致他们不一样呢?
Tomcat每次请求过来时在创建session时都会把这个webSocketContainer作为参数传进去所以对所有的session都生效了
我们来看看jetty
启动初始化时jetty把webSocketContainer的参数值设置给了这个过滤器,我们看看这个过滤器它的dofilter方法
其实在这个过滤器我们没有注册任何的访问URL,因为我们是通过spring提供的方式实现的websocket,如果我们在这个过滤器注册了访问URL那么所有过来的请求都会生效~可以继续跟后面的代码,其实就是调用的其他处理器来处理,这些处理器是在这里初始化的
每个url都有一个单独的处理器,这个实现是springboot提供的
spring给每一个websocket URL单独new 了一个处理器,jetty里面这个类是WebSocketServerFactory
其实jetty的处理方式是事件驱动模式设计,把所有的请求当做一个事件,不同的事件有自己对应的eventdriver来处理
重点是jetty在实例化这个对象的时候并没有把webSocketContainer所带的参数设置进去
这就导致了Tomcat生效jetty没有生效,其实你还会发现虽然jetty的buffer默认大小是64K,Tomcat是8K,可是jetty压测的时候CPU和内存都比Tomcat少,这是为什么呢?
原因是jetty用了对象池,不像tomcat来一次请求就new一个buffer,下面是jetty使用对象池的地方
后面简单做了netty的压测10000个连接消耗内存190M,吊炸天的存在