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