內存泄露排查之線程泄露

基礎

內存泄露(Memory Leak)

java中內存都是由jvm管理窍奋,垃圾回收由gc負責,所以一般情況下不會出現(xiàn)內存泄露問題捂掰,所以容易被大家忽略棘伴。

內存泄漏是指無用對象(不再使用的對象)持續(xù)占有內存或無用對象的內存得不到及時釋放乐尊,從而造成內存空間的浪費稱為內存泄漏。內存泄露有時不嚴重且不易察覺咬最,這樣開發(fā)者就不知道存在內存泄露邮辽,需要自主觀察,比較嚴重的時候港谊,沒有內存可以分配骇吭,直接oom。

主要和溢出做區(qū)分歧寺。

內存泄露現(xiàn)象

heap或者perm/metaspace區(qū)不斷增長, 沒有下降趨勢, 最后不斷觸發(fā)FullGC, 甚至crash.

如果低頻應用燥狰,可能不易發(fā)現(xiàn),但是最終情況還是和上述描述一致斜筐,內存一致增長

perm/metaspace泄露

這里存放class,method相關對象龙致,以及運行時常量對象. 如果一個應用加載了大量的class, 那么Perm區(qū)存儲的信息一般會比較大.另外大量的intern String對象也會導致該區(qū)不斷增長。

比較常見的一個是Groovy動態(tài)編譯class造成泄露顷链。這里就不展開了

heap泄露

比較常見的內存泄露

靜態(tài)集合類引起內存泄露

監(jiān)聽器:但往往在釋放對象的時候卻沒有記住去刪除這些監(jiān)聽器目代,從而增加了內存泄漏的機會。

各種連接,數據庫榛了、網絡在讶、IO等

內部類和外部模塊等的引用:內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的后繼類對象沒有釋放霜大。非靜態(tài)內部類的對象會隱式強引用其外圍對象构哺,所以在內部類未釋放時,外圍對象也不會被釋放战坤,從而造成內存泄漏

單例模式:不正確使用單例模式是引起內存泄露的一個常見問題曙强,單例對象在被初始化后將在JVM的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用湖笨,那么這個外部對象將不能被jvm正称炱耍回收,導致內存泄露

其它第三方類

本例(線程泄露)

本例現(xiàn)象

內存占用率達80%+左右慈省,并且持續(xù)上漲臀防,最高點到94%

yongGC比較頻繁,在內存比較高的時候,伴有FullGC

線程個個數比較多边败,最高點達到2w+(這個比較重要袱衷,可惜是后面才去關注這點)

日志伴有大量異常,主要是三類

fastJosn error

調用翻譯接口識別語種服務錯誤

對接算法提供的二方包請求錯誤

剛開始走的錯誤彎路

剛開始發(fā)現(xiàn)機器內存占用比較多笑窜,超過80%+致燥,這個時候思考和內存相關的邏輯

這個時候并沒有去觀察線程數量,根據現(xiàn)象 1排截、2嫌蚤、4,断傲、這個過程沒有發(fā)現(xiàn)現(xiàn)象3脱吱,排查無果后,重新定位問題發(fā)現(xiàn)現(xiàn)象3

由于現(xiàn)象4中的錯誤日志比較多认罩,加上內存占用高箱蝠,產生了如下想法(由于本例中很多服務通過mq消費開始)

現(xiàn)象4中的錯誤導致mq重試隊列任務增加,積壓的消息導致mq消費隊列任務增加垦垂,最終導致內存上升

由于異常宦搬,邏輯代碼中的異常重試線程池中的任務增加,最終導致任務隊列的長度一直增加劫拗,導致內存上升

解決彎路中的疑惑

定位異常

fastJson解析異常间校,光看錯誤會覺得踩到了fastJson的bug(fastJson在之前的版本中,寫入Long類型到Map中页慷,在解析的時候默認是用Int解析器解析撇簿,導致溢出錯誤聂渊。但是這個bug在后面的版本修復了,目前即使是放入Long類型四瘫,如果小于int極限值,默認是int解析欲逃,超過int極限找蜜,默認long。類中的變量為Long稳析。直接parse洗做,直接為Long類型),但是業(yè)務代碼中使用的是類直接parse彰居,發(fā)現(xiàn)二方包中的類使用了int诚纸,但是消息值有的超過int值

eas算法鏈路調用錯誤,之前就有(404)陈惰,但是沒有定位到具體原因畦徘,有知道的望指點下,這里用try catch做了處理

翻譯服務異常抬闯,這里沒定位到具體原因井辆,重啟應用后恢復,這里忘記了做try catch溶握,看來依賴外部服務需要全部try下

確認是否是業(yè)務邏輯中錯誤重試隊列問題

否杯缺,和業(yè)務相關才會走入重試流程,還在后面

確認是否是Mq消息隊列本以及Mq重試隊列 消息積壓導致

否睡榆,Mq做了消費隊列安全保護

consumer異步拉取broker中的消息萍肆,processQueue中消息過多就會控制拉取的速率。對于并發(fā)的處理場景胀屿, 存在三種控制的策略:

1. queue中的個數是否超過1000

2. 估算msg占用的內存大小是否超過100MB

3. queue中仍然存在的msg(多半是消費失敗的塘揣,且回饋broker失敗的)的offset的間隔,過大可能表示會有更多的重復碉纳,默認最大間隔是2000勿负。

流控源碼類:com.alibaba.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullMessage,圈中的變量在默認的類中都有初始值

metaq也會自己做動態(tài)線程調整劳曹,理論上當線程不夠用時奴愉,增加線程,adjustThreadPoolNumsThreshold默認10w铁孵,當線程比較多時锭硼,減少線程,但是代碼被注釋了蜕劝,理論上應該沒有自動調整過程檀头,所以這里也不會因為任務過多增加過多線程數

在start啟動的時候轰异,啟動了一批定時任務

定時任務中啟動了調整線程的定時任務

啟動調整任務

回歸正途的處理邏輯

經過上述分析,發(fā)現(xiàn)并不是因為異常導致的任務隊列增加過大導致暑始,這個時候搭独,發(fā)現(xiàn)了現(xiàn)象3,活動線程數明顯過多廊镜,肯定是線程泄露牙肝,gc不能回收,導致內存一直在增長嗤朴,所以到這里配椭,基本上就已經確認是問題由什么導致,接下來要做的就是確認是這個原因導致雹姊,以及定位到具體的代碼塊

如果沒有具體的監(jiān)控股缸,一般就是看內存,cpu吱雏,heap狀況敦姻,gc狀況等,最終依然無法定位到代碼塊的可以dump

登錄涉事機器

top坎背,觀察內存占用率(這里圖是重啟之后一段時間的)但是cpu占用率比較高替劈,很快就降下去了,這里耽誤了一下時間得滤,top -Hp pid,確認那個線程占用率高陨献,jstack看了下對應的線程在作甚

確認線程是否指定大小,未發(fā)現(xiàn)指定懂更,使用的默認值

查看heap,gc狀況

查看線程狀況眨业,可jstack線程,發(fā)現(xiàn)線程較多沮协,也能定位到龄捡,但是為了方便,遂dump一份數據詳細觀察堆棧

線程個數

cat /proc/{pid}/status (線程數竟然這么多)

由于線程數比較多慷暂,而依然可以創(chuàng)建聘殖,查看Linux普通用戶所允許創(chuàng)建的進程數,使用命令:cat /etc/security/limits.d/90-nproc.conf ,值比較到行瑞,遠超當前的個數

線程信息

線程狀態(tài)

定位到問題線程

AbstractMultiworkerIOReactor ==》 httpAsycClient ==》如圖所示不能直接定位到代碼塊奸腺,所以maven定位引用jar的服務 ==> 具體二方包

如果每次都new線程而不結束,gc中線程是root節(jié)點血久,如果線程沒有結束突照,不會被回收,所以如果創(chuàng)建大量運行的線程氧吐,會導致內存占用量上升讹蘑,但是線上到底能創(chuàng)建多少線程呢末盔?

問題代碼塊

方法開始(每次都初始化一個新的客戶端,底層封裝使用httpAsyncClient座慰,httpAsyncClient使用NIO模型陨舱,初始化包含一個boss,10個work線程)

方法結束(方法結束都調用了shutdow)

根據現(xiàn)象和對應線程堆棧信息版仔,能確定線程就是在這邊溢出隅忿,客戶端的shutDown方法關閉線程池失效,導致由于初始的線程都是NIO模式邦尊,沒有被結束,所以線程一直積壓增加优烧,可修改為單例模式蝉揍,限制系統(tǒng)使用一個線程池

httpAsyncClient部分源碼

啟動

線程池命名,也就是上面出現(xiàn)pool--thread-的線程

ioEventDispatch 線程

啟動

worker線程

worker線程名稱

IO worker運行詳細

worker線程實現(xiàn)

shutdown 這里就不做分析了,調用后畦娄,線程都會跳出死循環(huán)又沾,結束線程,關閉鏈接等好多清理動作

疑問

雖然每次方法調用都是new新的客戶端熙卡,但是結束finally中都調用了shutDown杖刷,為何會關閉失敗,上面使用單例模式驳癌,只是掩蓋了為什么每次new客戶端然后shutdown失效的原因

httpAsyncClient客戶端在請求失敗的情況下滑燃,httpclient.close()此處會導致主線程阻塞;經源碼發(fā)現(xiàn)close 方法內部颓鲜,在線程連接池關閉以后表窘, httpclient對應線程還處于運行之中,一直阻塞在epollWait甜滨,詳見上面的線程狀態(tài)乐严,這里目前沒有確定下為什么調用shutdown之后線程關閉失敗,也沒有任何異常日志衣摩,但是這是導致線程泄露的主要原因

在本地測試shutdown方法可正常關閉昂验,很是奇怪。如果各位有知道具體的原因的艾扮,望指教

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 721575865

群內提供免費的Java架構學習資料(里面有高可用既琴、高并發(fā)、高性能及分布式栏渺、Jvm性能調優(yōu)呛梆、Spring源碼,MyBatis磕诊,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己填物,不要再用"沒有時間“來掩飾自己思想上的懶惰纹腌!趁年輕,使勁拼滞磺,給未來的自己一個交代升薯!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市击困,隨后出現(xiàn)的幾起案子涎劈,更是在濱河造成了極大的恐慌,老刑警劉巖阅茶,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛛枚,死亡現(xiàn)場離奇詭異,居然都是意外死亡脸哀,警方通過查閱死者的電腦和手機蹦浦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撞蜂,“玉大人盲镶,你說我怎么就攤上這事◎蚬睿” “怎么了溉贿?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浦旱。 經常有香客問我宇色,道長,這世上最難降的妖魔是什么闽寡? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任代兵,我火速辦了婚禮,結果婚禮上爷狈,老公的妹妹穿的比我還像新娘植影。我一直安慰自己,他們只是感情好涎永,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布思币。 她就那樣靜靜地躺著,像睡著了一般羡微。 火紅的嫁衣襯著肌膚如雪谷饿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天妈倔,我揣著相機與錄音博投,去河邊找鬼。 笑死盯蝴,一個胖子當著我的面吹牛毅哗,可吹牛的內容都是我干的听怕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虑绵,長吁一口氣:“原來是場噩夢啊……” “哼尿瞭!你這毒婦竟也來了?” 一聲冷哼從身側響起翅睛,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤声搁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捕发,有當地人在樹林里發(fā)現(xiàn)了一具尸體疏旨,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年扎酷,在試婚紗的時候發(fā)現(xiàn)自己被綠了充石。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霞玄,死狀恐怖,靈堂內的尸體忽然破棺而出拉岁,到底是詐尸還是另有隱情坷剧,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布喊暖,位于F島的核電站惫企,受9級特大地震影響,放射性物質發(fā)生泄漏陵叽。R本人自食惡果不足惜狞尔,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巩掺。 院中可真熱鬧偏序,春花似錦、人聲如沸胖替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽独令。三九已至端朵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間燃箭,已是汗流浹背冲呢。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留招狸,地道東北人敬拓。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓邻薯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恩尾。 傳聞我的和親對象是個殘疾皇子弛说,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,097評論 1 32
  • 線程池ThreadPoolExecutor corepoolsize:核心池的大小,默認情況下翰意,在創(chuàng)建了線程池之后...
    irckwk1閱讀 724評論 0 0
  • 第一部分 來看一下線程池的框架圖冀偶,如下: 1醒第、Executor任務提交接口與Executors工具類 Execut...
    壓抑的內心閱讀 4,261評論 1 24
  • 在一個方法內部定義的變量都存儲在棧中,當這個函數運行結束后进鸠,其對應的棧就會被回收稠曼,此時,在其方法體中定義的變量將不...
    Y了個J閱讀 4,416評論 1 14
  • 這是平淡的一年客年,我跨過了40歲的大關霞幅。這是希望的一年,我終于看到夢寐以求的大海量瓜。這是充滿驚喜的一年司恳,妹妹為家里又添...
    路語旁集閱讀 184評論 0 0