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ù)等待。