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
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,吊炸天的存在
總結
以上是生活随笔為你收集整理的TOMCAT websocket 多连接内存泄漏与jetty对比分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot-websocket
- 下一篇: 解决webserver tcp连接大量C