一次線程溢出的實戰(zhàn)分析-創(chuàng)新互聯(lián)

問題:

一個線上的問題,隨著時間推移,線程沒有正常關(guān)閉導(dǎo)致的線程泄漏,現(xiàn)象是服務(wù)器內(nèi)存趨于百分之百。

創(chuàng)新互聯(lián)公司于2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目做網(wǎng)站、網(wǎng)站建設(shè)網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元西吉做網(wǎng)站,已為上家服務(wù),為西吉各地企業(yè)和個人服務(wù),聯(lián)系電話:13518219792解決辦法: ????????1. 借助指令分析。

使用 pslist -dmx pid配合jstack -l pid ,pslist結(jié)果如下圖。發(fā)現(xiàn)隨著時間推移(3分鐘增加一個),線程會越來愈多。而且線程的內(nèi)容會逐漸增加??梢詫栴}定位到定時任務(wù)(有個定時任務(wù)是3分鐘執(zhí)行一次),初步確定為由定時任務(wù)引發(fā)的問題。

2.查看jstack結(jié)果。

如下圖,發(fā)現(xiàn)新增的線程都有個共性,都是由一個很陌生的類com.mashape.unirest.http.utils.SyncIdleConnectionMonitorThread 創(chuàng)建,于是在項目中全局搜這個類,通過搜com.mashape.unirest,找到了代碼所在位置。

3. 分析代碼。

細看代碼,發(fā)現(xiàn)這是一個類似于發(fā)送http請求的工具類,只有這兩塊代碼。

這里有兩種解決辦法, 第一是查看源碼,分析原因,第二種就是直接調(diào)試代碼。

推薦用第二種(別問為什么,問就是因為簡單、快),我當時采用的是初中生物學(xué)到的控制變量法進行的測試,保持其他條件不變,①注掉第104和105-112行相關(guān)代碼、②只注釋第104行代碼、③只注釋105-112行代碼。這三種情況測試完以后發(fā)現(xiàn)①、②兩種情況不會復(fù)現(xiàn)線程溢出的問題,于是定位到問題出在設(shè)置超時時間這行代碼上(104行)。

至此,問題就找到了,我們只需要把104這行代碼初始化一次,比如放進靜態(tài)代碼塊,保證只會在類被加載的時候初始化一次,而不是每次執(zhí)行到這里就初始化,這樣就可以解決每次都會開啟線程的問題了。

4. 根本原因。

當然,作為一名優(yōu)秀的程序員,沒有查看源碼的好習(xí)慣明顯是不合格的(我怎么這么不要臉 - -!),通過分析源碼,截圖如下:

我發(fā)現(xiàn)Unirest.setTimeouts()這個方法內(nèi)部有一個refresh()的方法,點進去看到有這么一坨代碼:

其中最長的一行顯示不下了,我粘在這里:

RequestConfig clientConfig = RequestConfig.custom()
.setConnectTimeout(((Long)connectionTimeout).intValue())
.setSocketTimeout(((Long)socketTimeout).intValue())
.setConnectionRequestTimeout(((Long)socketTimeout).intValue())
.setProxy(proxy)
.build();

這塊代碼大概意思是,用我們之前setTimeouts方法參數(shù)里穿進去的超時時間初始化客戶端的配置信息,之后將配置信息寫入連接池并初始化連接池的一些信息,最后會開啟一個名為SyncIdleConnectionMonitorThread的線程來處理這個連接池,看線程名字的意思,是一個監(jiān)測閑置鏈接的線程,就是清理資源的,看一看里面的東西:

他用了個死循環(huán)不斷監(jiān)視沒有關(guān)閉的線程,并獲取當前線程對象的對象鎖,wait方法沒啥意義,就是為了防止循環(huán)太快搞死cup,每次都等5s再執(zhí)行。之后就是關(guān)閉資源的操作,然后繼續(xù)循環(huán),直到線程關(guān)閉時,再執(zhí)行isInterruptes()方法就會報錯拋異常,然后return退出線程??雌饋砗孟駴]什么問題,疑問就是我并不知道他會在哪里停止當前線程,畢竟這個線程不會自己關(guān)閉,這樣就會一直循環(huán)下去,但是鬼知道哪里會停止當前線程。

到這里還是沒有發(fā)現(xiàn)為什么,問題到底出在哪里?分析到這里的時候,我決定再細看一波代碼,當然還是從setTimeouts這里看起,終于,在一行一行看源碼以后,粗心的我發(fā)現(xiàn)了問題,就在線程類的構(gòu)造方法里,有這么一行不起眼的代碼:super.setDaemon(true); 于是Google找了Thread.setDaemon(true)是啥意思:通過Thread.setDaemon(true)設(shè)置為守護線程,在沒有用戶線程可服務(wù)時會自動離開。這里還很貼心的給出了實例,看樣子是說,只要主線程不死,守護線程就會一直存在。也就是說定時任務(wù)執(zhí)行一次,就會創(chuàng)建一個守護線程。

我和諧他和諧的和諧,這也太坑了吧??!

5. 總結(jié)

這次主要是用了pslist 和jstack 指令查看線程相關(guān)信息(linux系統(tǒng)可以使用top配合jstack,也可以借助其他插件,比如ali的arthas,用起來挺不錯),發(fā)現(xiàn)了異常線程,并通過這個信息定位到出錯的代碼位置,這時就要考驗分析代碼的基本功了。

辛辛苦苦分析了這么多,居然是這么個問題??梢姡谑褂脛e人的工具的時候,一定要好好查看api,或者多看幾個相關(guān)資料再下手,避免產(chǎn)生意想不到的問題。

另外,類似配置信息的這種東西,還是不要頻繁初始化的好,如果api里沒有詳細介紹使用細節(jié),可以看一看源碼,避免出錯(時間充裕的情況下)。

參考:

JAVA Thread.setDaemon用法

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

網(wǎng)站標題:一次線程溢出的實戰(zhàn)分析-創(chuàng)新互聯(lián)
網(wǎng)頁URL:http://bm7419.com/article36/hdppg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號網(wǎng)站收錄、定制網(wǎng)站、品牌網(wǎng)站建設(shè)、外貿(mào)建站、品牌網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)