1. 程式人生 > 實用技巧 >一次資源洩露問題排查紀錄

一次資源洩露問題排查紀錄

問題背景

前不久技術學院負責人讓我幫助維護下⼤講堂、積分管理等⼏個系統。這些系統功能都已經很穩定,也不會再有新的功能開發,但是⼤講堂系統自上線後,每過⼀段時間就會出錯,導致學員⽆法報名課程,希望我幫助解決下。這種累積下來的問題解決起來⽐較難,但是對能⼒提升幫助很大,我欣然接受了,所以就有了這次“艱苦”的問題排查和解決之旅。

解決思路

本人未參加過大講堂和積分系統的開發,解決問題⾸先要最⼤化的瞭解系統背景、問題發⽣時的系統表現等資訊。技術學院的同事和我說,負責系統的開發⼈員已經換過⼏波了,現在基本已經找不到了解系統的開發⼈員了,但是也反饋了以下資訊:
1、系統是16年公司應屆⽣培訓時做的項⽬。
2、其他系統運⾏良好,就是⼤講堂系統偶爾會報錯,導致學員⽆法報名,重啟下就好了,果然是“重啟⼤法”好啊。
3、系統出錯的頻率不⾼,但是貌似很有規律,⼤半個⽉左右出現⼀次。
根據以上描述,特別是出錯後“重啟⼤法”特別有效,出錯頻率基本固定,通過這兩點基本上可以斷定是資源洩露導致的問題。既然確定是資源洩露問題,對於 Java 應⽤來說排查⽅向也就確定為以下2個⽅向:
1、物件洩露
2、執行緒洩露
3. 處理過程
前文提過本人並未參與專案開發,所以整個排查大部分是通過 linux 和 jvm 的⼀些命令列輔助功能來進行的,下面是整個排查過程:
1、首先通過 jps 確定 Java 應用的程序 id

sudo -u tomcat jps -lv | grep qtscore

2、排查記憶體洩露

通過持續觀察 GC 日誌檔案 /home/q/www/qtscore/log/gc.log 檔案,發現 GC 執行頻率正常,特別是 Full GC 執行也並不頻繁,雖然記憶體使用量在持續增長,但是並不明顯。

為了確認業務程式碼是否存在洩漏,通過 jmap 查看了堆內物件分佈情況,切記此命令會導致程序暫停,如果是 qps 高或者響應時間要求高的應用慎用:

sudo -u tomcat jmap –F -histo 11035

存洩露的可能性。
3、排查執行緒洩露

top -H -p 11035

通過結果可以看出執行緒已經高達4038,應用中dubbo執行緒池預設配置是200個執行緒,tomcat執行緒池配置也是200個執行緒,所以這個執行緒數明顯不合理。參照另外一個同等應用,其執行緒數未超過500,所以可以初步判斷系統存線上程洩露,下⼀步只能檢視執行緒棧資訊了:

sudo -u tomcat jstack -l 11035 > /tmp/qtscore_stack.log

系統中存在100多個“New I/O boss"執行緒,這個應該是 netty 執行緒池的 boss 執行緒,到這裡基本上可以確認是執行緒池洩露引起的問題了,但是究竟是哪段程式碼引起的,執行緒棧中並無業務相關程式碼,只是 netty 執行緒池的程式碼,所以無法判斷出引起問題的程式碼源頭,只能先看看系統日誌中是否有價值的資訊了,發現系統日誌中頻繁報以下錯誤:

找到對應的原始碼進行檢視,發現這是⼀個 dubbo 服務介面,而 dubbo 底層用的就是 netty,種種跡象表明問題應該就出在這裡。仔細分析程式碼後,發現這個 dubbo 介面丟擲的異常 QtalkQueryException,居然沒有通過 api 暴露給呼叫方,也就是說呼叫方是無法正確反序列化這個異常的。初步推測可能就是這個原因導致 dubbo 未能正確處理執行緒池,導致執行緒池洩露了。將 QtalkQueryException 改為丟擲 IllegalStateException 異常,釋出上線,滿懷期待地等著問題被完美解決。過了一個星期後,通過 top 命令檢視程序的執行緒數,發現執行緒數又飆升到1千1百多了,看來問題還是沒有解決,只能再排查了,但是基本上可以確定是執行緒洩露問題,而且是 netty 引起的,可是執行緒棧中 netty 的 worker 執行緒棧中沒有任何業務相關的程式碼,應該是程式碼間接用到了 netty 執行緒池,但是一時沒有了頭緒。只有找同事諮詢一下了,和幾個同事討論後,大家都認為最有可能用到執行緒池的就是非同步請求,特別是一些HTTP客戶端,根據這個思路對程式碼進行排查後,發現系統中有如下程式碼:

這個 com.ning.http.client.AsyncHttpClient 底層用到了 netty 執行緒池,這個用法存在明顯錯誤,AsyncHttpClient 例項應該重用,而不應該每次使用時都建立一個。修改程式碼後重新發布上線,一週後再統計應用執行緒數,發現執行緒數穩定在350個左右,確認問題得到了解決。但是故事還沒有結束,個人覺得既然應用報錯,日誌應該有所體現才對,難道是被其他異常淹沒了?所以重新排查了出錯日期的日誌,果然發現了以下錯誤資訊:

通過系統命令 ulimit –a 發現系統最大使用者執行緒數是4096,所以問題是建立的執行緒超過了系統最大使用者執行緒數限制。

經驗總結

回顧這次問題排查經歷,其實這個問題排查起來並不難,最初的排查方向也是對的,但是整個過程還是花費了很長時間。回顧整個過程,總結出以下經驗和教訓:

  • 大規模使用的基礎元件(例如:Dubbo)不容易有問題,即使有問題也會被及時修復,所以還是要多分析自己編寫的程式碼。

  • 排查問題首先要仔細分析系統日誌,報錯的地方⼀般也就是引起問題的源頭,遵循和重視這個原則,通常可以大大提高排查問題的效率。

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。

  2. 關注公眾號 『java爛豬皮』,不定期分享原創知識。

  3. 同時可以期待後續文章ing