TOMCAT websocket 多连接内存泄漏与jetty对比分析
服務(wù)器環(huán)境8核 32G內(nèi)存
?
問題:
在5000個(gè)連接的時(shí)候Tomcat內(nèi)存基本吃滿
Tomcat 壓測:
| 連接數(shù) | 內(nèi)存消耗 | CPU |
| 1000 | 6.9G | 100% |
| 2000 | 12G | 100% |
| 3000 | 19G | 100% |
| 4000 | 25G | 100% |
| 4468 | 30G | 100% |
| 更多連接已經(jīng)無法建立 |
檢查下JVM內(nèi)存使用情況,其中老年代被占用了98.4%,有大量不能釋放的對象在heap。
老年代內(nèi)存大小31.5G,老年代使用31G
JETTY壓測
| 連接數(shù) | 內(nèi)存消耗 | CPU |
| 1000 | 1G | 25% |
| 3000 | 1G | 25% |
| 5000 | 1.1G | 25% |
| 10000 | 1.2G | 25% |
| 20000 | 1.4G | 50% |
內(nèi)存情況老年代被使用了79.4%,老年代總大小3.5G占用0.5G
通過現(xiàn)象我們可以看到j(luò)etty占用內(nèi)存非常小,而TOMCAT暫用卻非常多,但是國外論壇有人Tomcat卻可以壓到4萬的連接,是什么東西占用了這么多heap空間呢?
分析TOMCAT內(nèi)存占滿原因:
一,由于測試方便在本地啟動(dòng)程序建立了501個(gè)連接,并且用jmap生成了快照
二,我們用mat來分析下內(nèi)存的情況(也可以用jhat,但是太low了很多還需要人工肉眼看和計(jì)算)
我們可以看到WsFrameServer占用了2.9的heap空間對象個(gè)數(shù)恰好是我們建立的連接數(shù)
三,現(xiàn)在我們來分析WsFrameServer對象,到底什么東西可以占用這么大
我們可以看到WsFrameServer對象直接引用對象的heap空間HeapCharBuffer和HeapByteBuffer非常大
我們再來看看這個(gè)大對象什么引用在引用,通過分析我們知道了原來是WsFrameServer的messageBufferText成員變量
下面我們來看下源代碼
我們在WsFrameServer的父類中發(fā)現(xiàn)了這兩個(gè)大對象的引用
那么問題來了,是什么原因?qū)е逻@個(gè)對象很大的呢?我們繼續(xù)看源代碼什么地方用來它,特別是初始化的時(shí)候
我們檢查到,這個(gè)地方初始化的,初始化大小是wsSession.getMaxBinaryMessageBufferSize()
和wsSession.getMaxTextMessageBufferSize(),那么問題又來了,這兩個(gè)值有是從哪里來的呢?
我們查看源碼
有一個(gè)默認(rèn)值是8K,如果是8K的話內(nèi)存不至于溢出,看什么地方做了賦值
原來是這個(gè)地方做的賦值,那么webSocketContainer又是從哪里來的呢?
我們點(diǎn)進(jìn)去
這里要么是默認(rèn)值,要么是什么地方做了設(shè)置我們打斷點(diǎn)調(diào)試,而且這個(gè)默認(rèn)值是8K,肯定是什么地方修改了,那么我們在set方法打斷點(diǎn)
斷點(diǎn)來了,我們通過線程棧來分析下什么地方調(diào)到這里來的,跟著往上點(diǎn)
最后我們發(fā)現(xiàn)是這個(gè)類ServletServerContainerFactoryBean,這個(gè)類是spring提供的,我們是在這兒用到了,原來如此,內(nèi)存之所以這么大就是這里的設(shè)置導(dǎo)致的,這個(gè)設(shè)置的目的是讓websocket可以傳輸更大的消息
其實(shí)我們看到的Tomcat的webSocketContainer是實(shí)現(xiàn)了javax.websocket.webSocketContainer的,很明顯這個(gè)是J2EE的規(guī)范我們可以查下這個(gè)規(guī)范
https://docs.oracle.com/javaee/7/api/javax/websocket/WebSocketContainer.html
其實(shí)這個(gè)類就是在初始化的時(shí)候可以讓你設(shè)置websocket相關(guān)的一些參數(shù)
但是它設(shè)置了之后對所有session都生效了所以導(dǎo)致我們的連接數(shù)上不去,有什么辦法可以解決嗎,我們查到有另外的J2EE規(guī)范可以在運(yùn)行過程中動(dòng)態(tài)的修改而且是針對特定session的
那么問題來了,為什么jetty沒有出現(xiàn)這個(gè)問題,如果是J2EE的規(guī)范那么我們設(shè)置這個(gè)值JETTY也應(yīng)該出現(xiàn)這個(gè)問題,可是沒有!
我們再來對jetty進(jìn)行調(diào)試,發(fā)現(xiàn)這個(gè)配置對jetty不起作用
為什么不起作用呢?這又是另外的問題了~
后來通過源碼分析得知JETTY和TOMCAT的實(shí)現(xiàn)不一樣,不知道算不算BUG,個(gè)人認(rèn)為是JETTY的BUG~
什么代碼實(shí)現(xiàn)導(dǎo)致他們不一樣呢?
Tomcat每次請求過來時(shí)在創(chuàng)建session時(shí)都會(huì)把這個(gè)webSocketContainer作為參數(shù)傳進(jìn)去所以對所有的session都生效了
我們來看看jetty
啟動(dòng)初始化時(shí)jetty把webSocketContainer的參數(shù)值設(shè)置給了這個(gè)過濾器,我們看看這個(gè)過濾器它的dofilter方法
其實(shí)在這個(gè)過濾器我們沒有注冊任何的訪問URL,因?yàn)槲覀兪峭ㄟ^spring提供的方式實(shí)現(xiàn)的websocket,如果我們在這個(gè)過濾器注冊了訪問URL那么所有過來的請求都會(huì)生效~可以繼續(xù)跟后面的代碼,其實(shí)就是調(diào)用的其他處理器來處理,這些處理器是在這里初始化的
每個(gè)url都有一個(gè)單獨(dú)的處理器,這個(gè)實(shí)現(xiàn)是springboot提供的
spring給每一個(gè)websocket URL單獨(dú)new 了一個(gè)處理器,jetty里面這個(gè)類是WebSocketServerFactory
其實(shí)jetty的處理方式是事件驅(qū)動(dòng)模式設(shè)計(jì),把所有的請求當(dāng)做一個(gè)事件,不同的事件有自己對應(yīng)的eventdriver來處理
重點(diǎn)是jetty在實(shí)例化這個(gè)對象的時(shí)候并沒有把webSocketContainer所帶的參數(shù)設(shè)置進(jìn)去
這就導(dǎo)致了Tomcat生效jetty沒有生效,其實(shí)你還會(huì)發(fā)現(xiàn)雖然jetty的buffer默認(rèn)大小是64K,Tomcat是8K,可是jetty壓測的時(shí)候CPU和內(nèi)存都比Tomcat少,這是為什么呢?
原因是jetty用了對象池,不像tomcat來一次請求就new一個(gè)buffer,下面是jetty使用對象池的地方
后面簡單做了netty的壓測10000個(gè)連接消耗內(nèi)存190M,吊炸天的存在
總結(jié)
以上是生活随笔為你收集整理的TOMCAT websocket 多连接内存泄漏与jetty对比分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot-websocket
- 下一篇: YAML语言介绍