使用
導(dǎo)出線程堆棧:jstack? pid > c:/file.txt
線程狀態(tài)
Java官方定義的線程狀態(tài)有6種蛹批,定義在一個(gè)枚舉類中:
NEW,未啟動的線程第租,不會出現(xiàn)在Dump中措拇;
RUNNABLE,運(yùn)行中慎宾,包括就緒狀態(tài)和運(yùn)行中狀態(tài)丐吓, 調(diào)用start方法并獲取到鎖后進(jìn)入就緒狀態(tài),獲取到CPU后進(jìn)入運(yùn)行中狀態(tài)趟据;(當(dāng)線程遇到I/O或者調(diào)用suspend掛起線程時(shí)還是在運(yùn)行狀態(tài))券犁;
BLOCKED,受阻塞并等待監(jiān)視器鎖汹碱,被某個(gè)鎖(synchronizers)給block住了粘衬;
WATING,無限期等待某個(gè)condition或monitor發(fā)生咳促,一般停留在park, wait,?sleep,join (未設(shè)置超時(shí)時(shí)間)等語句里稚新;
TIMED_WATING,有時(shí)限的等待另一個(gè)線程的特定操作跪腹,如sleep褂删,加上超時(shí)時(shí)間的wait;
TERMINATED,已退出的冲茸。
堆棧信息中不存在NEW和TERMINATED狀態(tài)屯阀。
監(jiān)視器(Monitor)
Monitor是用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段缅帘,它可以看成是對象或者 Class的鎖。每一個(gè)對象都有难衰,也僅有一個(gè) monitor钦无。
Monitor可以類比為一個(gè)特殊的房間,這個(gè)房間中有一些被保護(hù)的數(shù)據(jù)盖袭,Monitor保證每次只能有一個(gè)線程能進(jìn)入這個(gè)房間進(jìn)行訪問被保護(hù)的數(shù)據(jù)失暂,進(jìn)入房間即為持有Monitor,退出房間即為釋放Monitor苍凛。
當(dāng)一個(gè)線程需要訪問受保護(hù)的數(shù)據(jù)(即需要獲取對象的Monitor)時(shí)趣席,它會首先在entry-set入口隊(duì)列中排隊(duì)(這里并不是真正的按照排隊(duì)順序),如果沒有其他線程正在持有對象的Monitor醇蝴,那么它會和entry-set隊(duì)列和wait-set隊(duì)列中的被喚醒的其他線程進(jìn)行競爭(即通過CPU調(diào)度)宣肚,選出一個(gè)線程來獲取對象的Monitor,執(zhí)行受保護(hù)的代碼段悠栓,執(zhí)行完畢后釋放Monitor霉涨,如果已經(jīng)有線程持有對象的Monitor,那么需要等待其釋放Monitor后再進(jìn)行競爭惭适。
再說一下wait-set隊(duì)列笙瑟。當(dāng)一個(gè)線程擁有Monitor后,經(jīng)過某些條件的判斷(比如用戶取錢發(fā)現(xiàn)賬戶沒錢)癞志,這個(gè)時(shí)候需要調(diào)用Object的wait方法往枷,線程就釋放了Monitor,進(jìn)入wait-set隊(duì)列凄杯,等待Object的notify方法(比如用戶向賬戶里面存錢)错洁。當(dāng)該對象調(diào)用了notify方法或者notifyAll方法后,wait-set中的線程就會被喚醒戒突,然后在wait-set隊(duì)列中被喚醒的線程和entry-set隊(duì)列中的線程一起通過CPU調(diào)度來競爭對象的Monitor屯碴,最終只有一個(gè)線程能獲取對象的Monitor。
進(jìn)入?yún)^(qū)(Entry Set):表示線程通過synchronized要求獲取對象的鎖膊存。如果對象未被鎖住导而,則進(jìn)入擁有者,否則在進(jìn)入?yún)^(qū)等待隔崎;
擁有者(The Owner):表示某一線程成功競爭到對象鎖今艺;
等待區(qū)(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖爵卒,并在等待區(qū)等待被喚醒虚缎;
一個(gè) Monitor在某個(gè)時(shí)刻,只能被一個(gè)線程擁有技潘,該線程就是“Active Thread”遥巴,而其它線程都是“Waiting Thread”,分別在兩個(gè)隊(duì)列“ Entry Set”和“Wait Set”里面等候享幽;
在“Entry Set”中等待的線程狀態(tài)是“Waiting for monitor entry”铲掐,而在“Wait Set”中等待的線程狀態(tài)是“in Object.wait()”;?
我們稱被 synchronized保護(hù)起來的代碼段為臨界區(qū)值桩,當(dāng)一個(gè)線程申請進(jìn)入臨界區(qū)時(shí)摆霉,它就進(jìn)入了 “Entry Set”隊(duì)列;
線程堆棧解釋
線程名稱:若未設(shè)置奔坟,自動命名"thread-x" 或 "線程池名稱-thread-x"携栋,線程池若未設(shè)置,自動命名"pool-x"咳秉,若是守護(hù)線程后面會顯示“daemon”婉支;
線程優(yōu)先級:最低1,最高10澜建,默認(rèn)5向挖,優(yōu)先級高的不一定先執(zhí)行;
nid:tid映射的操作系統(tǒng)中的線程id炕舵,這里是用16進(jìn)制的表示何之;
線程動作(本地線程狀態(tài)):線程狀態(tài)產(chǎn)生的原因
---runnable:此時(shí)線程狀態(tài)一般為RUNNABLE;
---in Object.wait():等待區(qū)等待咽筋,此時(shí)線程狀態(tài)為WAITING或TIMED_WAITING溶推;
---waiting for monitor entry:進(jìn)入?yún)^(qū)等待,等待進(jìn)入一個(gè)臨界區(qū)奸攻,所以它在”Entry Set“隊(duì)列中等待蒜危,線程狀態(tài)一般是 BLOCKED;
---waiting on condition:等待區(qū)等待舞箍、被park舰褪,等待另一個(gè)條件的發(fā)生來把自己喚醒,線程狀態(tài)是:WAITING(parking疏橄,一直等那個(gè)條件發(fā)生)或 TIMED_WAITING?(parking或sleeping占拍,定時(shí)的,那個(gè)條件不到來捎迫,也將定時(shí)喚醒自己)晃酒;
---sleeping:休眠的線程,調(diào)用了Thread.sleep()窄绒;
調(diào)用修飾:表示線程在方法調(diào)用時(shí)贝次,額外的重要的操作
---locked <地址/鎖ID> 目標(biāo):使用synchronized申請對象鎖成功,監(jiān)視器的擁有者彰导;
---waiting to lock <地址/鎖ID> 目標(biāo):使用synchronized申請對象鎖未成功蛔翅,在進(jìn)入?yún)^(qū)等待敲茄;
---waiting on <地址/鎖ID> 目標(biāo):使用synchronized申請對象鎖成功后,釋放鎖在等待區(qū)等待山析;
---parking to wait for <地址/鎖ID> 目標(biāo):park是基本的線程阻塞原語堰燎,不通過監(jiān)視器在對象上阻塞,與synchronized體系不同笋轨;
尖括號中表示鎖ID秆剪,這個(gè)是系統(tǒng)自動產(chǎn)生的,我們只需要知道每次打印的堆棧爵政,同一個(gè)ID表示是同一個(gè)鎖即可仅讽。
---當(dāng)一個(gè)線程占有一個(gè)鎖的時(shí)候,線程的堆棧中會打印--locked<0x00000000d77d50c8>
---當(dāng)一個(gè)線程正在等待其它線程釋放該鎖钾挟,線程堆棧中會打印--waiting to lock<0x00000000d77d50c8>
---當(dāng)一個(gè)線程占有一個(gè)鎖洁灵,但又執(zhí)行到該鎖的wait()方法上,線程堆棧中首先打印locked掺出,然后又會打印--waiting on <0x00000000d77d50c8>
問題分析
大量線程在waiting for monitor entry
可能是一個(gè)全局鎖阻塞住了大量線程处渣,隨著時(shí)間流逝,waiting for monitor entry 的線程越來越多蛛砰,沒有減少的趨勢罐栈,可能意味著某些線程在臨界區(qū)里呆的時(shí)間太長了,以至于越來越多新線程遲遲無法進(jìn)入臨界區(qū)泥畅;
大量線程在waiting on condition
可能是它們又跑去獲取第三方資源荠诬,尤其是第三方網(wǎng)絡(luò)資源,遲遲獲取不到Response位仁,導(dǎo)致大量線程進(jìn)入等待狀態(tài)柑贞,所以如果你發(fā)現(xiàn)有大量的線程都處在 Wait on condition,從線程堆椖羟溃看钧嘶,正等待網(wǎng)絡(luò)讀寫,這可能是一個(gè)網(wǎng)絡(luò)瓶頸的征兆琳疏,因?yàn)榫W(wǎng)絡(luò)阻塞導(dǎo)致線程無法執(zhí)行有决;
線程在in Object.wait()
當(dāng)線程獲得了 Monitor,如果發(fā)現(xiàn)線程繼續(xù)運(yùn)行的條件沒有滿足空盼,它則調(diào)用對象的 wait() 方法书幕,放棄了 Monitor,進(jìn)入 “Wait Set”隊(duì)列揽趾,一般都是RMI相關(guān)線程(RMI RenewClean台汇、 GC Daemon、RMI Reaper),GC線程(Finalizer)苟呐,引用對象垃圾回收線程(Reference Handler)等系統(tǒng)線程處于這種狀態(tài)痒芝;
線程處于parking to wait for ?<>?(a java.util.concurrent.SynchronousQueue$TransferStack)
首先,本線程肯定是在等待某個(gè)條件的發(fā)生牵素,來把自己喚醒吼野。其次,SynchronousQueue 并不是一個(gè)隊(duì)列两波,只是線程之間移交信息的機(jī)制,當(dāng)我們把一個(gè)元素放入到 SynchronousQueue 中時(shí)必須有另一個(gè)線程正在等待接受移交的任務(wù)闷哆,因此這就是本線程在等待的條件腰奋。
CPU占用率很高,響應(yīng)很慢
先找到占用CPU的進(jìn)程抱怔,然后再定位到對應(yīng)的線程劣坊,最后分析出對應(yīng)的堆棧信息。
Linux環(huán)境下屈留,使用 top -Hp pid 可以查看進(jìn)程下各個(gè)線程的cpu 內(nèi)存情況局冰,使用printf “%x\n” <threadid> 可將線程ID轉(zhuǎn)成16進(jìn)制(即上面的nid);
Windows環(huán)境下灌危,沒有自帶命令康二,可以使用Process Explorer工具查看;
CPU占用率不高勇蝙,但響應(yīng)很慢
在整個(gè)請求的過程中多次執(zhí)行Thread Dump然后進(jìn)行對比沫勿,取得?BLOCKED?狀態(tài)的線程列表,通常是因?yàn)榫€程停在了I/O味混、數(shù)據(jù)庫連接或網(wǎng)絡(luò)連接的地方产雹。
在線分析
使用jca工具分析