jstack和線程dump分析

jstack用于打印出給定的java進(jìn)程ID或core file或遠(yuǎn)程調(diào)試服務(wù)的Java堆棧信息,如果是在64位機(jī)器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:

jstack [-l][F] pid

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

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

死鎖,Deadlock(重點關(guān)注)

執(zhí)行中,Runnable

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

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

暫停,Suspended

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

阻塞,Blocked(重點關(guān)注)

停止,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) {……} 申請進(jìn)入了臨界區(qū),從而進(jìn)入了下圖1中的“Entry Set”隊列,但該 obj 對應(yīng)的 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),但這里指定了時間,到達(dá)指定的時間后自動退出等待狀態(tài);parking指線程處于掛起中。

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

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)”,對于本例而言,是因為本線程調(diào)用了 java.lang.Object.wait(long timeout) 而進(jìn)入等待狀態(tài)。

2)“Wait Set”中等待的線程狀態(tài)就是“ in Object.wait() ”。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運行的條件沒有滿足,它則調(diào)用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進(jìn)入 “Wait Set”隊列。只有當(dāng)別的線程在該對象上調(diào)用了 notify() 或者 notifyAll() ,“ Wait Set”隊列中線程才得到機(jī)會去競爭,但是只有一個線程獲得對象的 Monitor,恢復(fù)到運行態(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(對應(yīng)于? locked <0x00000000aa672478> );當(dāng)執(zhí)行到 lock.wait(timeout);,線程就放棄了 Monitor 的所有權(quán),進(jìn)入“Wait Set”隊列(對應(yīng)于? waiting on <0x00000000aa672478> )。

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

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

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

1.1 Runnable

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

1.2 Wait on condition

這種狀態(tài)時由于獲取了鎖,進(jìn)入了臨界區(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保護(hù)起來的代碼段為臨界區(qū)。當(dāng)一個線程申請進(jìn)入臨界區(qū)時,它就進(jìn)入了 “Entry Set”隊列。對應(yīng)的 code就像:

synchronized(obj) {

.........

}

這時有兩種可能性:

該 monitor不被其它線程擁有, Entry Set里面也沒有其它等待線程。本線程即成為相應(yīng)類或者對象的 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ū)的設(shè)置,是為了保證其內(nèi)部的代碼執(zhí)行的原子性和完整性。但是因為臨界區(qū)在任何時間只允許線程串行通過,這 和我們多線程的程序的初衷是相反的。 如果在多線程的程序中,大量使用 synchronized,或者不適當(dāng)?shù)氖褂昧怂?,會造成大量線程在臨界區(qū)的入口等待,造成系統(tǒng)的性能大幅下降。如果在線程 DUMP中發(fā)現(xiàn)了這個情況,應(yīng)該審查源碼,改進(jìn)程序。

??????? 現(xiàn)在我們再來看現(xiàn)在線程為什么會進(jìn)入 “Wait Set”。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運行的條件沒有滿足,它則調(diào)用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進(jìn)入 “Wait Set”隊列。只有當(dāng)別的線程在該對象上調(diào)用了 notify() 或者 notifyAll() , “ Wait Set”隊列中線程才得到機(jī)會去競爭,但是只有一個線程獲得對象的 Monitor,恢復(fù)到運行態(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)????

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

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

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

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

[java]?view plain?copy

synchronized(obj)?{????

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

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

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

}????

線程的執(zhí)行中,先用 synchronized 獲得了這個對象的 Monitor(對應(yīng)于 locked <0xef63beb8> )。當(dāng)執(zhí)行到 obj.wait(), 線程即放棄了 Monitor的所有權(quán),進(jìn)入 “wait set”隊列(對應(yīng)于 waiting on <0xef63beb8> )。

???????? 往往在你的程序中,會出現(xiàn)多個類似的線程,他們都有相似的 DUMP信息。這也可能是正常的。比如,在程序中,有多個服務(wù)線程,設(shè)計成從一個隊列里面讀取請求數(shù)據(jù)。這個隊列就是 lock以及 waiting on的對象。當(dāng)隊列為空的時候,這些線程都會在這個隊列上等待,直到隊列有了數(shù)據(jù),這些線程被 Notify,當(dāng)然只有一個線程獲得了 lock,繼續(xù)執(zhí)行,而其它線程繼續(xù)等待。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容