jstack和線程dump分析

jstack用于打印出給定的java進程ID或core file或遠程調試服務的Java堆棧信息凉逛,如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:

jstack [-l][F] pid

如果java程序崩潰生成core文件玛痊,jstack工具可以用來獲得core文件的java stack和native stack的信息汰瘫,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題。另外擂煞,jstack工具還可以附屬到正在運行的java程序中混弥,看到當時運行的java程序的java stack和native stack的信息, 如果現(xiàn)在運行的java程序呈現(xiàn)hung的狀態(tài),jstack是非常有用的对省。進程處于hung死狀態(tài)可以用-F強制打出stack蝗拿。

dump 文件里,值得關注的線程狀態(tài)有:

死鎖蒿涎,Deadlock(重點關注)

執(zhí)行中哀托,Runnable

等待資源,Waiting on condition(重點關注)

等待獲取監(jiān)視器劳秋,Waiting on monitor entry(重點關注)

暫停仓手,Suspended

對象等待中,Object.wait() 或 TIMED_WAITING

阻塞玻淑,Blocked(重點關注)

停止嗽冒,Parked

1、實例說明

在摘了另一篇博客的三種場景:

實例一:Waiting to lock 和 Blocked

[java]?view plain?copy

"RMI?TCP?Connection(267865)-172.16.5.25"?daemon?prio=10?tid=0x00007fd508371000?nid=0x55ae?waiting?for?monitor?entry?[0x00007fd4f8684000]??

???java.lang.Thread.State:?BLOCKED?(on?object?monitor)??

at?org.apache.log4j.Category.callAppenders(Category.java:201)??

-?waiting?to?lock?<0x00000000acf4d0c0>?(a?org.apache.log4j.Logger)??

at?org.apache.log4j.Category.forcedLog(Category.java:388)??

at?org.apache.log4j.Category.log(Category.java:853)??

at?org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)??

at?com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)??

說明:

1)線程狀態(tài)是 Blocked补履,阻塞狀態(tài)添坊。說明線程等待資源超時!

2)“ waiting to lock <0x00000000acf4d0c0>”指箫锤,線程在等待給這個 0x00000000acf4d0c0 地址上鎖(英文可描述為:trying to obtain? 0x00000000acf4d0c0 lock)贬蛙。

3)在 dump 日志里查找字符串 0x00000000acf4d0c0,發(fā)現(xiàn)有大量線程都在等待給這個地址上鎖谚攒。如果能在日志里找到誰獲得了這個鎖(如locked < 0x00000000acf4d0c0 >)阳准,就可以順藤摸瓜了。

4)“waiting for monitor entry”說明此線程通過 synchronized(obj) {……} 申請進入了臨界區(qū)五鲫,從而進入了下圖1中的“Entry Set”隊列,但該 obj 對應的 monitor 被其他線程擁有岔擂,所以本線程在 Entry Set 隊列中等待位喂。

5)第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 乱灵。tid指Java Thread id塑崖。nid指native線程的id。prio是線程優(yōu)先級痛倚。[0x00007fd4f8684000]是線程棧起始地址规婆。

[java]?view plain?copy

"RMI?TCP?Connection(idle)"?daemon?prio=10?tid=0x00007fd50834e800?nid=0x56b2?waiting?on?condition?[0x00007fd4f1a59000]??

???java.lang.Thread.State:?TIMED_WAITING?(parking)??

at?sun.misc.Unsafe.park(Native?Method)??

-?parking?to?waitfor??<0x00000000acd84de8>?(a?java.util.concurrent.SynchronousQueue$TransferStack)??

at?java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)??

at?java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424)??

at?java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323)??

at?java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874)??

at?java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945)??

at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)??

at?java.lang.Thread.run(Thread.java:662)??

說明:

1)“TIMED_WAITING (parking)”中的 timed_waiting 指等待狀態(tài),但這里指定了時間,到達指定的時間后自動退出等待狀態(tài)抒蚜;parking指線程處于掛起中掘鄙。

2)“waiting on condition”需要與堆棧中的“parking to wait for? <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)”結合來看。首先嗡髓,本線程肯定是在等待某個條件的發(fā)生操漠,來把自己喚醒。其次饿这,SynchronousQueue 并不是一個隊列浊伙,只是線程之間移交信息的機制,當我們把一個元素放入到 SynchronousQueue 中時必須有另一個線程正在等待接受移交的任務长捧,因此這就是本線程在等待的條件嚣鄙。

3)別的就看不出來了。

實例三:in Obejct.wait() 和 TIMED_WAITING

[java]?view plain?copy

"RMI?RenewClean-[172.16.5.19:28475]"?daemon?prio=10?tid=0x0000000041428800?nid=0xb09?in?Object.wait()?[0x00007f34f4bd0000]??

???java.lang.Thread.State:?TIMED_WAITING?(on?object?monitor)??

at?java.lang.Object.wait(Native?Method)??

-?waiting?on?<0x00000000aa672478>?(a?java.lang.ref.ReferenceQueue$Lock)??

at?java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)??

-?locked?<0x00000000aa672478>?(a?java.lang.ref.ReferenceQueue$Lock)??

at?sun.rmi.transport.DGCClient$EndpointEntry$RenewCleanThread.run(DGCClient.java:516)??

at?java.lang.Thread.run(Thread.java:662)??

說明:

1)“TIMED_WAITING (on object monitor)”串结,對于本例而言哑子,是因為本線程調用了 java.lang.Object.wait(long timeout) 而進入等待狀態(tài)。

2)“Wait Set”中等待的線程狀態(tài)就是“ in Object.wait() ”奉芦。當線程獲得了 Monitor赵抢,進入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運行的條件沒有滿足声功,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法烦却,放棄了 Monitor,進入 “Wait Set”隊列先巴。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() 其爵,“ Wait Set”隊列中線程才得到機會去競爭,但是只有一個線程獲得對象的 Monitor伸蚯,恢復到運行態(tài)摩渺。

3)RMI RenewClean 是 DGCClient 的一部分。DGC 指的是 Distributed GC剂邮,即分布式垃圾回收摇幻。

4)請注意,是先 locked <0x00000000aa672478>挥萌,后 waiting on <0x00000000aa672478>绰姻,之所以先鎖再等同一個對象,請看下面它的代碼實現(xiàn):

static private class? Lock { };

private Lock lock = new Lock();

public Reference remove(long timeout)

{

synchronized (lock) {

Reference r = reallyPoll();

if (r != null) return r;

for (;;) {

lock.wait(timeout);

r = reallyPoll();

……

}

}

即引瀑,線程的執(zhí)行中狂芋,先用 synchronized 獲得了這個對象的 Monitor(對應于? locked <0x00000000aa672478> );當執(zhí)行到 lock.wait(timeout);憨栽,線程就放棄了 Monitor 的所有權帜矾,進入“Wait Set”隊列(對應于? waiting on <0x00000000aa672478> )翼虫。

5)從堆棧信息看,是正在清理 remote references to remote objects 屡萤,引用的租約到了珍剑,分布式垃圾回收在逐一清理呢。

2.2. 線程的狀態(tài)分析

正如我們剛看到的那樣灭衷,線程的狀態(tài)是一個重要的指標次慢,它會顯示在線程 Stacktrace的頭一行結尾的地方。那么線程常見的有哪些狀態(tài)呢翔曲?線程在什么樣的情況下會進入這種狀態(tài)呢迫像?我們能從中發(fā)現(xiàn)什么線索?

1.1 Runnable

該狀態(tài)表示線程具備所有運行條件瞳遍,在運行隊列中準備操作系統(tǒng)的調度闻妓,或者正在運行。

1.2 Wait on condition

這種狀態(tài)時由于獲取了鎖,進入了臨界區(qū),但是等待某種條件或者是sleep了 阻塞在這矩欠。

1.3 Waiting for monitor entry 和 in Object.wait()

在多線程的 JAVA程序中,實現(xiàn)線程之間的同步均唉,就要說說 Monitor。 Monitor是 Java中用以實現(xiàn)線程之間的互斥與協(xié)作的主要手段肚菠,它可以看成是對象或者 Class的鎖舔箭。每一個對象都有,也僅有一個 monitor蚊逢。每個 Monitor在某個時刻层扶,只能被一個線程擁有,該線程就是 “Active Thread”烙荷,而其它線程都是 “Waiting Thread”镜会,分別在兩個隊列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”终抽,而在 “Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”戳表。

先看 “Entry Set”里面的線程。我們稱被 synchronized保護起來的代碼段為臨界區(qū)昼伴。當一個線程申請進入臨界區(qū)時匾旭,它就進入了 “Entry Set”隊列。對應的 code就像:

synchronized(obj) {

.........

}

這時有兩種可能性:

該 monitor不被其它線程擁有亩码, Entry Set里面也沒有其它等待線程季率。本線程即成為相應類或者對象的 Monitor的 Owner野瘦,執(zhí)行臨界區(qū)的代碼

該 monitor被其它線程擁有描沟,本線程在 Entry Set隊列中等待飒泻。

在第一種情況下,線程將處于 “Runnable”的狀態(tài)吏廉,而第二種情況下泞遗,線程 DUMP會顯示處于 “waiting for monitor entry”。如下所示:

[java]?view plain?copy

"Thread-0"?prio=10?tid=0x08222eb0?nid=0x9?waiting?for?monitor?entry?[0xf927b000..0xf927bdb8]????


at?testthread.WaitThread.run(WaitThread.java:39)????


-?waiting?to?lock?<0xef63bf08>?(a?java.lang.Object)????


-?locked?<0xef63beb8>?(a?java.util.ArrayList)????


at?java.lang.Thread.run(Thread.java:595)????

? ? 臨界區(qū)的設置席覆,是為了保證其內部的代碼執(zhí)行的原子性和完整性史辙。但是因為臨界區(qū)在任何時間只允許線程串行通過,這 和我們多線程的程序的初衷是相反的佩伤。 如果在多線程的程序中聊倔,大量使用 synchronized,或者不適當?shù)氖褂昧怂玻瑫斐纱罅烤€程在臨界區(qū)的入口等待耙蔑,造成系統(tǒng)的性能大幅下降。如果在線程 DUMP中發(fā)現(xiàn)了這個情況孤荣,應該審查源碼甸陌,改進程序。

??????? 現(xiàn)在我們再來看現(xiàn)在線程為什么會進入 “Wait Set”盐股。當線程獲得了 Monitor钱豁,進入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運行的條件沒有滿足疯汁,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法牲尺,放棄了 Monitor,進入 “Wait Set”隊列涛目。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() 秸谢, “ Wait Set”隊列中線程才得到機會去競爭,但是只有一個線程獲得對象的 Monitor霹肝,恢復到運行態(tài)估蹄。在 “Wait Set”中的線程, DUMP中表現(xiàn)為: in Object.wait()沫换,類似于:

[java]?view plain?copy

"Thread-1"?prio=10?tid=0x08223250?nid=0xa?in?Object.wait()?[0xef47a000..0xef47aa38]????

????????at?java.lang.Object.wait(Native?Method)????

-?waiting?on?<0xef63beb8>?(a?java.util.ArrayList)????

at?java.lang.Object.wait(Object.java:474)????

at?testthread.MyWaitThread.run(MyWaitThread.java:40)????

-?locked?<0xef63beb8>?(a?java.util.ArrayList)????

at?java.lang.Thread.run(Thread.java:595)????

仔細觀察上面的 DUMP信息臭蚁,你會發(fā)現(xiàn)它有以下兩行:

- locked <0xef63beb8> (a java.util.ArrayList)

- waiting on <0xef63beb8> (a java.util.ArrayList)?

這里需要解釋一下,為什么先 lock了這個對象讯赏,然后又 waiting on同一個對象呢垮兑?讓我們看看這個線程對應的代碼:

[java]?view plain?copy

synchronized(obj)?{????

???????.........????

???????obj.wait();????

???????.........????

}????

線程的執(zhí)行中,先用 synchronized 獲得了這個對象的 Monitor(對應于 locked <0xef63beb8> )漱挎。當執(zhí)行到 obj.wait(), 線程即放棄了 Monitor的所有權系枪,進入 “wait set”隊列(對應于 waiting on <0xef63beb8> )。

???????? 往往在你的程序中磕谅,會出現(xiàn)多個類似的線程私爷,他們都有相似的 DUMP信息雾棺。這也可能是正常的。比如衬浑,在程序中捌浩,有多個服務線程,設計成從一個隊列里面讀取請求數(shù)據(jù)工秩。這個隊列就是 lock以及 waiting on的對象尸饺。當隊列為空的時候,這些線程都會在這個隊列上等待助币,直到隊列有了數(shù)據(jù)浪听,這些線程被 Notify,當然只有一個線程獲得了 lock眉菱,繼續(xù)執(zhí)行馋辈,而其它線程繼續(xù)等待。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末倍谜,一起剝皮案震驚了整個濱河市迈螟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尔崔,老刑警劉巖答毫,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異季春,居然都是意外死亡洗搂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門载弄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耘拇,“玉大人,你說我怎么就攤上這事宇攻”古眩” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵逞刷,是天一觀的道長嘉涌。 經(jīng)常有香客問我,道長夸浅,這世上最難降的妖魔是什么仑最? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮帆喇,結果婚禮上警医,老公的妹妹穿的比我還像新娘。我一直安慰自己坯钦,他們只是感情好预皇,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布损敷。 她就那樣靜靜地躺著,像睡著了一般深啤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上路星,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天溯街,我揣著相機與錄音,去河邊找鬼洋丐。 笑死呈昔,一個胖子當著我的面吹牛,可吹牛的內容都是我干的友绝。 我是一名探鬼主播堤尾,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迁客!你這毒婦竟也來了郭宝?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掷漱,失蹤者是張志新(化名)和其女友劉穎粘室,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卜范,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡衔统,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了海雪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锦爵。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奥裸,靈堂內的尸體忽然破棺而出险掀,到底是詐尸還是另有隱情,我是刑警寧澤湾宙,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布迷郑,位于F島的核電站,受9級特大地震影響创倔,放射性物質發(fā)生泄漏嗡害。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一畦攘、第九天 我趴在偏房一處隱蔽的房頂上張望霸妹。 院中可真熱鬧,春花似錦知押、人聲如沸叹螟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罢绽。三九已至畏线,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間良价,已是汗流浹背寝殴。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留明垢,地道東北人蚣常。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像痊银,于是被迫代替她去往敵國和親抵蚊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容