<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      tomcat為什么假死了.md

      現象

      我們生產最近有個服務偶爾會掛掉,接口報錯"connection reset by peer",上服務器curl也是同樣報錯,意思連接被server拒絕了。

      通過dump以及日志分析,我們已經知道了問題代碼所在,就是使用easyexcel上傳、解析文件,開發同學沒有做分頁,導致內存溢出。這點在easyexcel文檔也有提到:參見

      內存溢出后,觸發頻繁的full gc,由于gc很難有效回收內存,所以程序拋出了OutOfMemoryError,原因是:Java heap space。

      關于OOM的異常原因,我們也需要知道,有如下幾種:

      Java heap space
      內存無法分配新的對象。典型的場景是:內存不足,內存泄漏,分配的對象過大。

      GC Overhead limit exceeded
      gc回收異常,多次發生了98%的時間用于gc,但只回收2%的內存。典型的場景是:內存不足,內存泄漏。

      Metaspace
      元空間不足。典型的場景是:元空間設置太小,程序異常創建過多的Class。

      Direct buffer memory
      直接內存不足。典型的場景是:直接內存設置太小,直接內存泄漏。

      Unable to create new native thread
      無法創建新的線程。典型的場景是:系統ulimit -u設置太小。

      Requested array size exceeds VM limi
      數組大小太大。典型的場景是:new ClassA[Long.MAX_VALUE]

      CodeCache
      jit編譯緩存溢出。典型的場景是jit緩存設置過小。

      注意,我們上面提到問題的是內存溢出,而不是內存泄漏,兩者有本質的區別。
      內存溢出,通常是分配大對象,應用內存不足,通常分配多點空間就可以解決問題,而且所占的內存用完還是可以被回收的。
      內存泄漏,則是程序有問題,內存是無法被回收的,分配再多的空間也都會被慢慢消耗完。

      打個比方,內存溢出只是人長得不好看,不是壞人,內存泄漏則長得不好看,也是壞人。當然兩者我們都需要對其進行優化。

      通過我們的觀察也發現確實如此,經過一段時間后,文件解析數據處理完,內存就被回收了,也沒有full gc了。

      但問題來了,此時應用http請求還是繼續報錯的,依然是"connection reset by peer"。這點就不好理解了,應用恢復了,為什么tomcat沒有恢復,tomcat線程此時在做什么?從日志也看不到tomcat相關錯誤,tomcat假死了

      從監控上看,掛掉之前tomcat的請求線程數和連接數沒有什么波動。

      我們的兩個核心問題是:

      • 什么是"connection reset by peer"?
      • tomcat線程此時處于什么狀態?

      連接報錯

      我們搜索源碼,并沒有拋出"connection reset by peer"的代碼,也就是可能是jvm層面的拋出,或者系統層面的拋出。

      既然是跟connection相關,那我們就用netstat命令看下當前進程的連接情況:

      netstat -tlnp | grep 8100      
      netstat -anp | grep 8100  
      

      從輸出可以發現,有一個特殊的101,我們用正常的進程看一般都是0。

      這個參數叫:backlog,表示連接等待隊列的長度,對應tomcat的acceptCount參數,默認是100。

      當連接超過這個值時,就會報"connection reset by peer",我們當前是101,所以新來的請求就被拒絕了。

      tomcat線程模型

      對于第二個問題,tomcat線程正在干什么。一般我們可以通過jstack pid導出線程堆棧分析,不過當我們的服務運行一段時間,例如好幾天后,執行jstack,jmap都會報錯,似乎是某些信息被系統清除掉,這點我還找到根本原因,如果你知道答案請告知我一下。

      幸好有arthas,我們可以通過thread命令,查看線程和其堆棧信息。

      thread -n 10  #top 10 cpu
      thread 1 #展示線程1的堆棧
      thread --all  #展示所有線程  
      thread --all | grep http #展示http線程  
      

      我們可以看到,tomcat的10個核心線程還是在的,且處于waitting狀態。
      處于waitting狀態是因為它在等任務執行,從堆棧可以看出是阻塞在TaskQueue.take方法,org.apache.tomcat.util.threads.TaskQueue是tomcat中的LinkedBlockingQueue,是生產者-消費者模型,take方法阻塞表示當前隊列是空的,沒有任務需要執行,一旦有任務放入TaskQueue,take方法就會喚醒,進入Runnable狀態。

      這點就很奇怪了,前面說連接隊列都滿了,但tomcat任務隊列確是空的,執行線程都處于等待任務狀態,一邊滿載一邊空閑。

      要搞清楚這個問題,需要我們對tomcat線程模型有所了解。tomcat支持幾種IO模型,BIO、NIO、AIO(NIO2)、APR,我們可以通過server.tomcat.protocol參數進行設置,默認用的是NIO,NIO是一種同步非阻塞IO。

      NIO的核心目的是可以用少量線程處理大量連接,在linux用select/poll/epoll實現。

      NIO在很多中間件都有應用,kafka,redis,rocketmq,gateway等等,可以說涉及到網絡處理的都離不開NIO。

      AIO是真正的異步IO,但Linux對其支持不夠完善,且NIO已經足夠高效,所以NIO用得最多。

      reactor模型

      如何更好的實現NIO是個問題,就好像我們實現某個功能要用到設計模式一樣,reactor就是NIO一種實現模式。doug lee在scalable io in java總結了3種模型:

      單reactor單線程
      整個過程由一個線程完成,包括創建連接,讀寫數據,業務處理。redis 6.0以前的版本就是這種模式,實現起來簡單,沒有線程切換,加鎖的開銷。缺點是單線程不能發揮多核cpu的優勢,如果有一個業務處理阻塞了,那么整個服務都會阻塞。

      單reactor多線程
      接收連接(accept)和IO讀寫還是由一個線程完成,但業務處理會提交給業務線程池,業務處理不會阻塞整個服務。

      多reactor多線程
      接收連接由一個main reactor處理,建立連接后將其注冊到sub reactor上,每個sub reactor都是單reactor多線程模式。

      tomcat的實現

      tomcat的NIO由3種線程實現,分別是:Acceptor線程、Poller線程、請求處理線程。

      對于請求處理線程池我們比較熟悉,常用的兩個參數:

      server.tomcat.minSpareThreads = 10    
      server.tomcat.maxThreads = 200  
      

      對應到reactor模型,可以看成它是一種多reactor多線程模型,Acceptor線程負責建立連接,然后將建立好的連接注冊到Poller,由Poller進行讀寫。Poller讀寫后創建請求,將其交給請求處理線程池。
      Acceptor和Poller線程對應的run方法都是一個死循環,源源不斷的接收連接、讀寫連接。

      從reactor模型上看,在多核cpu下,多reactor多線程模型可以獲得更高的效率,但tomcat10以下默認只能有一個Acceptor和一個Poller線程。

      源碼分析

      源碼位置:org.apache.tomcat.util.net.NioEndpoint#startInternal,開啟Acceptor線程和Poller線程:

      源碼位置:org.apache.tomcat.util.net.Acceptor#run,while循環,建立連接:

      源碼位置:org.apache.tomcat.util.net.NioEndpoint.Poller#run,while循環,讀取數據:

      源碼位置:org.apache.tomcat.util.net.AbstractEndpoint#processSocket,將封裝好的請求交給請求線程池處理:

      關于tomcat線程池有一個點要注意,它和jdk不一樣的是,它是先開啟核心線程,當任務超過核心線程數,就繼續開啟至最大線程數,如果還超過才進入等待隊列。

      水落石出

      通過上面的分析,讓我們回到問題出現時的這張圖

      可以看到,Acceptor和Poller線程消失了!

      這樣我們現象就很好解釋了,Acceptor沒有拿新的連接來處理了,此時連接在系統層面積壓,tomcat請求處理線程空閑。

      我們重啟后再執行一下thread命令,正常的是:

      從Acceptor源碼上看,它捕獲了異常,但對于OOM,選擇重新拋出,Acceptor線程就中斷了,可見OOM對于tomcat來說是個致命異常,一旦程序有此類報錯,需要優化,否則可能導致整個服務異常。
      且Acceptor和Poller線程拋出這個位置在打印日志之前,所以也看不到錯誤日志,這點似乎不太好,但最新的tomcat版本也是保持如此。

      如果要獲得這個日志,我們也可以通過Thread的全局異常來捕獲:

      Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
          if (t.getName().equals("http-nio-8100-Acceptor")) {
              log.error("tomcat Acceptor error", t);
          }
      });
      

      總結

      OOM異常對于tomcat服務來說是致命的,發現即需要處理。
      對于內存泄漏來說,留有時間給我們dump內存分析。但對于內存溢出來說,由于其會回收,可能在某個時間OOM,順便把tomcat打掛了,然后就回收了,此時我們去dump也未必有用,所以-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath 參數是很有必要需要加上的。

      更多分享,歡迎關注我的github:https://github.com/jmilktea/jtea

      posted @ 2025-06-12 10:01  jtea  閱讀(4488)  評論(12)    收藏  舉報
      主站蜘蛛池模板: 国产无遮挡裸体免费久久| 亚洲熟妇一区二区三个区| 中文字幕不卡在线播放| 亚洲精品一区二区三区色| 免费无码一区无码东京热| 少妇伦子伦精品无吗| 久久婷婷综合色丁香五月| 亚洲精品一区二区三区在| 中文字幕国产精品av| 99RE8这里有精品热视频| 国产中文三级全黄| 亚洲精品不卡无码福利在线观看| 高清无码爆乳潮喷在线观看| 四虎在线成人免费观看| аⅴ天堂中文在线网| 成 人色 网 站 欧美大片在线观看 | 精品亚洲精品日韩精品| 久久精品国产亚洲夜色av网站| 日本一区二区久久人妻高清| 亚洲欧美高清在线精品一区二区| 国产精品亚洲二区在线看| 国产欧美综合在线观看第十页 | 久久婷婷五月综合色国产免费观看| 疯狂做受xxxx高潮视频免费| 亚洲久悠悠色悠在线播放| 人人做人人澡人人人爽| 欧美人与禽2o2o性论交| 国产精品一区二区色综合| 又爽又黄又无遮挡的激情视频| 盱眙县| A级毛片100部免费看| 国产高清一区二区不卡| 99久久国语露脸精品国产| 成人精品视频一区二区三区| 少妇夜夜春夜夜爽试看视频| 欧洲人与动牲交α欧美精品| 亚洲精品国产av一区二区| 午夜免费视频国产在线| 国产露脸无套对白在线播放| 亚洲av区一区二区三区| 97精品国产91久久久久久久|