1 Thread Dump介紹
1.1 什么是Thread Dump
Thread Dump是非常有用的診斷Java應(yīng)用問(wèn)題的工具加袋。每一個(gè)Java虛擬機(jī)都有及時(shí)生成所有線程在某一點(diǎn)狀態(tài)的thread-dump的能力区赵,雖然各個(gè) Java虛擬機(jī)打印的thread dump略有不同,但是 大多都提供了當(dāng)前活動(dòng)線程的快照捺信,及JVM中所有Java線程的堆棧跟蹤信息,堆棧信息一般包含完整的類名及所執(zhí)行的方法穿撮,如果可能的話還有源代碼的行數(shù)移斩。
1.2 Thread Dump特點(diǎn)
- 能在各種操作系統(tǒng)下使用;
- 能在各種Java應(yīng)用服務(wù)器下使用咬腋;
- 能在生產(chǎn)環(huán)境下使用而不影響系統(tǒng)的性能羹膳;
- 能將問(wèn)題直接定位到應(yīng)用程序的代碼行上;
1.3 Thread Dump抓取
一般當(dāng)服務(wù)器掛起根竿,崩潰或者性能低下時(shí)陵像,就需要抓取服務(wù)器的線程堆棧(Thread Dump)用于后續(xù)的分析。在實(shí)際運(yùn)行中寇壳,往往一次 dump的信息醒颖,還不足以確認(rèn)問(wèn)題。為了反映線程狀態(tài)的動(dòng)態(tài)變化壳炎,需要接連多次做thread dump泞歉,每次間隔10-20s,建議至少產(chǎn)生三次 dump信息匿辩,如果每次 dump都指向同一個(gè)問(wèn)題腰耙,我們才確定問(wèn)題的典型性。
-
操作系統(tǒng)命令獲取ThreadDump
- ps –ef | grep java
- kill -3 <pid>
注意:
一定要謹(jǐn)慎, 一步不慎就可能讓服務(wù)器進(jìn)程被殺死铲球。kill -9 命令會(huì)殺死進(jìn)程沟优。
-
JVM 自帶的工具獲取線程堆棧
- jps 或 ps –ef | grep java (獲取PID)
- jstack [-l ] <pid> | tee -a jstack.log(獲取ThreadDump)
2 Thread Dump分析
2.1 Thread Dump信息
-
頭部信息:時(shí)間,JVM信息
2011-11-02 19:05:06 Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
-
線程INFO信息塊:
1. "Timer-0" daemon prio=10 tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000] # 線程名稱:Timer-0睬辐;線程類型:daemon;優(yōu)先級(jí): 10宾肺,默認(rèn)是5溯饵; # JVM線程id:tid=0xac190c00,JVM內(nèi)部線程的唯一標(biāo)識(shí)(通過(guò)java.lang.Thread.getId()獲取锨用,通常用自增方式實(shí)現(xiàn))丰刊。 # 對(duì)應(yīng)系統(tǒng)線程id(NativeThread ID):nid=0xaef,和top命令查看的線程pid對(duì)應(yīng)增拥,不過(guò)一個(gè)是10進(jìn)制啄巧,一個(gè)是16進(jìn)制寻歧。(通過(guò)命令:top -H -p pid,可以查看該進(jìn)程的所有線程信息) # 線程狀態(tài):in Object.wait()秩仆; # 起始棧地址:[0xae77d000]码泛,對(duì)象的內(nèi)存地址,通過(guò)JVM內(nèi)存查看工具澄耍,能夠看出線程是在哪兒個(gè)對(duì)象上等待噪珊; 2. java.lang.Thread.State: TIMED_WAITING (on object monitor) 3. at java.lang.Object.wait(Native Method) 4. -waiting on <0xb3885f60> (a java.util.TaskQueue) # 繼續(xù)wait 5. at java.util.TimerThread.mainLoop(Timer.java:509) 6. -locked <0xb3885f60> (a java.util.TaskQueue) # 已經(jīng)locked 7. at java.util.TimerThread.run(Timer.java:462)
Java thread statck trace:是上面2-7行的信息。到目前為止這是最重要的數(shù)據(jù)齐莲,Java stack trace提供了大部分信息來(lái)精確定位問(wèn)題根源痢站。
-
Java thread statck trace詳解:
堆棧信息應(yīng)該逆向解讀:程序先執(zhí)行的是第7行,然后是第6行选酗,依次類推阵难。
- locked <0xb3885f60> (a java.util.ArrayList) - waiting on <0xb3885f60> (a java.util.ArrayList)
也就是說(shuō)對(duì)象先上鎖,鎖住對(duì)象0xb3885f60芒填,然后釋放該對(duì)象鎖呜叫,進(jìn)入waiting狀態(tài)。為啥會(huì)出現(xiàn)這樣的情況呢氢烘?看看下面的java代碼示例怀偷,就會(huì)明白:
synchronized(obj) { ......... obj.wait(); ......... }
如上,線程的執(zhí)行過(guò)程播玖,先用 synchronized 獲得了這個(gè)對(duì)象的 Monitor(對(duì)應(yīng)于 locked <0xb3885f60> )椎工。當(dāng)執(zhí)行到 obj.wait(),線程即放棄了 Monitor的所有權(quán)蜀踏,進(jìn)入 “wait set”隊(duì)列(對(duì)應(yīng)于 waiting on <0xb3885f60> )维蒙。
在堆棧的第一行信息中,進(jìn)一步標(biāo)明了線程在代碼級(jí)的狀態(tài)果覆,例如:
java.lang.Thread.State: TIMED_WAITING (parking)
解釋如下:
|blocked| > This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released. |blocked (on thin lock)| > This is the same state asblocked, but the lock in question is a thin lock. |waiting| > This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object. |sleeping| > This thread calledjava.lang.Thread.sleep(). |parked| > This thread calledjava.util.concurrent.locks.LockSupport.park(). |suspended| > The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.
2.2 Thread狀態(tài)分析
線程的狀態(tài)是一個(gè)很重要的東西颅痊,因此thread dump中會(huì)顯示這些狀態(tài),通過(guò)對(duì)這些狀態(tài)的分析局待,能夠得出線程的運(yùn)行狀況斑响,進(jìn)而發(fā)現(xiàn)可能存在的問(wèn)題。線程的狀態(tài)在Thread.State這個(gè)枚舉類型中定義:
public enum State
{
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
-
NEW:
每一個(gè)線程钳榨,在堆內(nèi)存中都有一個(gè)對(duì)應(yīng)的Thread對(duì)象舰罚。Thread t = new Thread();當(dāng)剛剛在堆內(nèi)存中創(chuàng)建Thread對(duì)象,還沒(méi)有調(diào)用t.start()方法之前薛耻,線程就處在NEW狀態(tài)营罢。在這個(gè)狀態(tài)上,線程與普通的java對(duì)象沒(méi)有什么區(qū)別饼齿,就僅僅是一個(gè)堆內(nèi)存中的對(duì)象饲漾。
-
RUNNABLE:
該狀態(tài)表示線程具備所有運(yùn)行條件蝙搔,在運(yùn)行隊(duì)列中準(zhǔn)備操作系統(tǒng)的調(diào)度,或者正在運(yùn)行考传。 這個(gè)狀態(tài)的線程比較正常吃型,但如果線程長(zhǎng)時(shí)間停留在在這個(gè)狀態(tài)就不正常了,這說(shuō)明線程運(yùn)行的時(shí)間很長(zhǎng)(存在性能問(wèn)題)伙菊,或者是線程一直得不得執(zhí)行的機(jī)會(huì)(存在線程饑餓的問(wèn)題)败玉。
-
BLOCKED:
線程正在等待獲取java對(duì)象的監(jiān)視器(也叫內(nèi)置鎖),即線程正在等待進(jìn)入由
synchronized
保護(hù)的方法或者代碼塊镜硕。synchronized用來(lái)保證原子性运翼,任意時(shí)刻最多只能由一個(gè)線程進(jìn)入該臨界區(qū)域,其他線程只能排隊(duì)等待兴枯。 -
WAITING:
處在該線程的狀態(tài)血淌,正在等待某個(gè)事件的發(fā)生,只有特定的條件滿足财剖,才能獲得執(zhí)行機(jī)會(huì)悠夯。而產(chǎn)生這個(gè)特定的事件,通常都是另一個(gè)線程躺坟。也就是說(shuō)沦补,如果不發(fā)生特定的事件,那么處在該狀態(tài)的線程一直等待咪橙,不能獲取執(zhí)行的機(jī)會(huì)夕膀。比如:
- A線程調(diào)用了obj對(duì)象的
obj.wait()
方法,如果沒(méi)有線程調(diào)用obj.notify
或obj.notifyAll美侦,那么A線程就沒(méi)有辦法恢復(fù)運(yùn)行产舞; - 如果A線程調(diào)用了
LockSupport.park()
,沒(méi)有別的線程調(diào)用LockSupport.unpark(A)
菠剩,那么A沒(méi)有辦法恢復(fù)運(yùn)行易猫。
- A線程調(diào)用了obj對(duì)象的
-
TIMED_WAITING:
J.U.C中很多與線程相關(guān)類,都提供了限時(shí)版本和不限時(shí)版本的API具壮。TIMED_WAITING意味著線程調(diào)用了限時(shí)版本的API准颓,正在等待時(shí)間流逝。當(dāng)?shù)却龝r(shí)間過(guò)去后棺妓,線程一樣可以恢復(fù)運(yùn)行辟灰。如果線程進(jìn)入了WAITING狀態(tài)惑申,一定要特定的事件發(fā)生才能恢復(fù)運(yùn)行胰耗;而處在TIMED_WAITING的線程欺抗,如果特定的事件發(fā)生或者是時(shí)間流逝完畢友浸,都會(huì)恢復(fù)運(yùn)行。
-
TERMINATED:
線程執(zhí)行完畢泞遗,執(zhí)行完run方法正常返回旷太,或者拋出了運(yùn)行時(shí)異常而結(jié)束,線程都會(huì)停留在這個(gè)狀態(tài)批旺。這個(gè)時(shí)候線程只剩下Thread對(duì)象了幌陕,沒(méi)有什么用了。
2.3 關(guān)鍵狀態(tài)分析
-
Wait on condition:The thread is either sleeping or waiting to be notified by another thread.
該狀態(tài)說(shuō)明它在等待另一個(gè)條件的發(fā)生汽煮,來(lái)把自己?jiǎn)拘巡ǎ蛘吒纱嗨钦{(diào)用了 sleep(n)。此時(shí)線程狀態(tài)大致為以下幾種:
- java.lang.Thread.State: WAITING (parking):一直等那個(gè)條件發(fā)生暇赤;
- java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時(shí)的心例,那個(gè)條件不到來(lái),也將定時(shí)喚醒自己鞋囊。
-
Waiting for Monitor Entry 和 in Object.wait():The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for individual methods.
在多線程的JAVA程序中止后,實(shí)現(xiàn)線程之間的同步,就要說(shuō)說(shuō) Monitor溜腐。 Monitor是Java中用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段译株,它可以看成是對(duì)象或者Class的鎖。每一個(gè)對(duì)象都有挺益,也僅有一個(gè) Monitor歉糜。下面這個(gè)圖,描述了線程和 Monitor之間關(guān)系望众,以及線程的狀態(tài)轉(zhuǎn)換圖:
A Java Monitor And Thread如上圖匪补,每個(gè)Monitor在某個(gè)時(shí)刻,只能被一個(gè)線程擁有黍檩,該線程就是 “ActiveThread”叉袍,而其它線程都是 “Waiting Thread”,分別在兩個(gè)隊(duì)列“Entry Set”和“Wait Set”里等候刽酱。在“Entry Set”中等待的線程狀態(tài)是“Waiting for monitor entry”喳逛,而在“Wait Set”中等待的線程狀態(tài)是“in Object.wait()”。
先看“Entry Set”里面的線程棵里。我們稱被 synchronized保護(hù)起來(lái)的代碼段為臨界區(qū)润文。當(dāng)一個(gè)線程申請(qǐng)進(jìn)入臨界區(qū)時(shí),它就進(jìn)入了“Entry Set”隊(duì)列殿怜。對(duì)應(yīng)的 code就像:
synchronized(obj) { ......... }
這時(shí)有兩種可能性:
- 該 monitor不被其它線程擁有典蝌, Entry Set里面也沒(méi)有其它等待線程。本線程即成為相應(yīng)類或者對(duì)象的 Monitor的 Owner头谜,執(zhí)行臨界區(qū)的代碼骏掀。
- 該 monitor被其它線程擁有,本線程在 Entry Set隊(duì)列中等待。
在第一種情況下截驮,線程將處于 “Runnable”的狀態(tài)笑陈,而第二種情況下,線程 DUMP會(huì)顯示處于 “waiting for monitor entry”葵袭。如下:
"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í)行的原子性和完整性。但是因?yàn)榕R界區(qū)在任何時(shí)間只允許線程串行通過(guò)坡锡,這和我們多線程的程序的初衷是相反的蓬网。如果在多線程的程序中,大量使用 synchronized鹉勒,或者不適當(dāng)?shù)氖褂昧怂妫瑫?huì)造成大量線程在臨界區(qū)的入口等待,造成系統(tǒng)的性能大幅下降贸弥。如果在線程 DUMP中發(fā)現(xiàn)了這個(gè)情況窟坐,應(yīng)該審查源碼,改進(jìn)程序绵疲。
再看“Wait Set”里面的線程哲鸳。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后盔憨,如果發(fā)現(xiàn)線程繼續(xù)運(yùn)行的條件沒(méi)有滿足徙菠,它則調(diào)用對(duì)象(一般就是被 synchronized 的對(duì)象)的 wait() 方法,放棄 Monitor郁岩,進(jìn)入 “Wait Set”隊(duì)列婿奔。只有當(dāng)別的線程在該對(duì)象上調(diào)用了 notify() 或者 notifyAll(),“Wait Set”隊(duì)列中線程才得到機(jī)會(huì)去競(jìng)爭(zhēng)问慎,但是只有一個(gè)線程獲得對(duì)象的Monitor萍摊,恢復(fù)到運(yùn)行態(tài)。在 “Wait Set”中的線程如叼, DUMP中表現(xiàn)為: in Object.wait()冰木。如下:
"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)
綜上,一般CPU很忙時(shí)笼恰,則關(guān)注runnable的線程踊沸,CPU很閑時(shí),則關(guān)注waiting for monitor entry的線程社证。
-
JDK 5.0 的 Lock
上面提到如果 synchronized和 monitor機(jī)制運(yùn)用不當(dāng)逼龟,可能會(huì)造成多線程程序的性能問(wèn)題。在 JDK 5.0中追葡,引入了 Lock機(jī)制腺律,從而使開發(fā)者能更靈活的開發(fā)高性能的并發(fā)多線程程序奕短,可以替代以往 JDK中的 synchronized和 Monitor的 機(jī)制。但是匀钧,要注意的是篡诽,因?yàn)?Lock類只是一個(gè)普通類,JVM無(wú)從得知 Lock對(duì)象的占用情況榴捡,所以在線程 DUMP中,也不會(huì)包含關(guān)于 Lock的信息朱浴, 關(guān)于死鎖等問(wèn)題吊圾,就不如用 synchronized的編程方式容易識(shí)別。
2.4 關(guān)鍵狀態(tài)示例
-
顯示BLOCKED狀態(tài)
package jstack; public class BlockedState { private static Object object = new Object(); public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { synchronized (object) { long begin = System.currentTimeMillis(); long end = System.currentTimeMillis(); // 讓線程運(yùn)行5分鐘,會(huì)一直持有object的監(jiān)視器 while ((end - begin) <= 5 * 60 * 1000) { } } } }; new Thread(task, "t1").start(); new Thread(task, "t2").start(); } }
先獲取object的線程會(huì)執(zhí)行5分鐘翰蠢,這5分鐘內(nèi)會(huì)一直持有object的監(jiān)視器项乒,另一個(gè)線程無(wú)法執(zhí)行處在BLOCKED狀態(tài):
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode): "DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x1314 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "t2" prio=6 tid=0x27d7a800 nid=0x1350 waiting for monitor entry [0x2833f000] java.lang.Thread.State: BLOCKED (on object monitor) at jstack.BlockedState$1.run(BlockedState.java:17) - waiting to lock <0x1cfcdc00> (a java.lang.Object) at java.lang.Thread.run(Thread.java:662) "t1" prio=6 tid=0x27d79400 nid=0x1338 runnable [0x282ef000] java.lang.Thread.State: RUNNABLE at jstack.BlockedState$1.run(BlockedState.java:22) - locked <0x1cfcdc00> (a java.lang.Object) at java.lang.Thread.run(Thread.java:662)
通過(guò)thread dump可以看到:t2線程確實(shí)處在BLOCKED (on object monitor)。waiting for monitor entry 等待進(jìn)入synchronized保護(hù)的區(qū)域梁沧。
-
顯示W(wǎng)AITING狀態(tài)
package jstack; public class WaitingState { private static Object object = new Object(); public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { synchronized (object) { long begin = System.currentTimeMillis(); long end = System.currentTimeMillis(); // 讓線程運(yùn)行5分鐘,會(huì)一直持有object的監(jiān)視器 while ((end - begin) <= 5 * 60 * 1000) { try { // 進(jìn)入等待的同時(shí),會(huì)進(jìn)入釋放監(jiān)視器 object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; new Thread(task, "t1").start(); new Thread(task, "t2").start(); } }
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode): "DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x1734 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "t2" prio=6 tid=0x27d7e000 nid=0x17f4 in Object.wait() [0x2833f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x1cfcdc00> (a java.lang.Object) at java.lang.Object.wait(Object.java:485) at jstack.WaitingState$1.run(WaitingState.java:26) - locked <0x1cfcdc00> (a java.lang.Object) at java.lang.Thread.run(Thread.java:662) "t1" prio=6 tid=0x27d7d400 nid=0x17f0 in Object.wait() [0x282ef000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x1cfcdc00> (a java.lang.Object) at java.lang.Object.wait(Object.java:485) at jstack.WaitingState$1.run(WaitingState.java:26) - locked <0x1cfcdc00> (a java.lang.Object) at java.lang.Thread.run(Thread.java:662)
可以發(fā)現(xiàn)t1和t2都處在WAITING (on object monitor)檀何,進(jìn)入等待狀態(tài)的原因是調(diào)用了in Object.wait()。通過(guò)J.U.C包下的鎖和條件隊(duì)列廷支,也是這個(gè)效果频鉴,大家可以自己實(shí)踐下。
-
顯示TIMED_WAITING狀態(tài)
package jstack; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TimedWaitingState { // java的顯示鎖,類似java對(duì)象內(nèi)置的監(jiān)視器 private static Lock lock = new ReentrantLock(); // 鎖關(guān)聯(lián)的條件隊(duì)列(類似于object.wait) private static Condition condition = lock.newCondition(); public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { // 加鎖,進(jìn)入臨界區(qū) lock.lock(); try { condition.await(5, TimeUnit.MINUTES); } catch (InterruptedException e) { e.printStackTrace(); } // 解鎖,退出臨界區(qū) lock.unlock(); } }; new Thread(task, "t1").start(); new Thread(task, "t2").start(); } }
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode): "DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x169c waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "t2" prio=6 tid=0x27d7d800 nid=0xc30 waiting on condition [0x2833f000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x1cfce5b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2116) at jstack.TimedWaitingState$1.run(TimedWaitingState.java:28) at java.lang.Thread.run(Thread.java:662) "t1" prio=6 tid=0x280d0c00 nid=0x16e0 waiting on condition [0x282ef000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x1cfce5b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2116) at jstack.TimedWaitingState$1.run(TimedWaitingState.java:28) at java.lang.Thread.run(Thread.java:662)
可以看到t1和t2線程都處在java.lang.Thread.State: TIMED_WAITING (parking)恋拍,這個(gè)parking代表是調(diào)用的JUC下的工具類垛孔,而不是java默認(rèn)的監(jiān)視器。
3 案例分析
3.1 問(wèn)題場(chǎng)景
- CPU飆高施敢,load高周荐,響應(yīng)很慢
- 一個(gè)請(qǐng)求過(guò)程中多次dump;
- 對(duì)比多次dump文件的runnable線程僵娃,如果執(zhí)行的方法有比較大變化概作,說(shuō)明比較正常。如果在執(zhí)行同一個(gè)方法默怨,就有一些問(wèn)題了讯榕;
- 查找占用CPU最多的線程
- 使用命令:
top -H -p pid
(pid為被測(cè)系統(tǒng)的進(jìn)程號(hào)),找到導(dǎo)致CPU高的線程ID先壕,對(duì)應(yīng)thread dump信息中線程的nid瘩扼,只不過(guò)一個(gè)是十進(jìn)制,一個(gè)是十六進(jìn)制垃僚;- 在thread dump中集绰,根據(jù)top命令查找的線程id,查找對(duì)應(yīng)的線程堆棧信息谆棺;
- CPU使用率不高但是響應(yīng)很慢
進(jìn)行dump栽燕,查看是否有很多thread struck在了i/o罕袋、數(shù)據(jù)庫(kù)等地方,定位瓶頸原因碍岔;
- 請(qǐng)求無(wú)法響應(yīng)
多次dump浴讯,對(duì)比是否所有的runnable線程都一直在執(zhí)行相同的方法,如果是的蔼啦,恭喜你榆纽,鎖住了!
3.2 死鎖
死鎖經(jīng)常表現(xiàn)為程序的停頓捏肢,或者不再響應(yīng)用戶的請(qǐng)求奈籽。從操作系統(tǒng)上觀察,對(duì)應(yīng)進(jìn)程的CPU占用率為零鸵赫,很快會(huì)從top或prstat的輸出中消失衣屏。
比如在下面這個(gè)示例中,是個(gè)較為典型的死鎖情況:
"Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
..0x02d3fd68]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock <0x22c19f18> (a java.lang.Object)
- locked <0x22c19f20> (a java.lang.Object)
"Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
..0x02cff9e8]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock <0x22c19f20> (a java.lang.Object)
- locked <0x22c19f18> (a java.lang.Object)
在 JAVA 5中加強(qiáng)了對(duì)死鎖的檢測(cè)辩棒。線程 Dump中可以直接報(bào)告出 Java級(jí)別的死鎖狼忱,如下所示:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),
which is held by "Thread-1"
3.3 熱鎖
熱鎖,也往往是導(dǎo)致系統(tǒng)性能瓶頸的主要因素一睁。其表現(xiàn)特征為:由于多個(gè)線程對(duì)臨界區(qū)钻弄,或者鎖的競(jìng)爭(zhēng),可能出現(xiàn):
- 頻繁的線程的上下文切換:從操作系統(tǒng)對(duì)線程的調(diào)度來(lái)看卖局,當(dāng)線程在等待資源而阻塞的時(shí)候斧蜕,操作系統(tǒng)會(huì)將之切換出來(lái),放到等待的隊(duì)列砚偶,當(dāng)線程獲得資源之后批销,調(diào)度算法會(huì)將這個(gè)線程切換進(jìn)去,放到執(zhí)行隊(duì)列中染坯。
- 大量的系統(tǒng)調(diào)用:因?yàn)榫€程的上下文切換均芽,以及熱鎖的競(jìng)爭(zhēng),或者臨界區(qū)的頻繁的進(jìn)出单鹿,都可能導(dǎo)致大量的系統(tǒng)調(diào)用掀宋。
- 大部分CPU開銷用在“系統(tǒng)態(tài)”:線程上下文切換,和系統(tǒng)調(diào)用仲锄,都會(huì)導(dǎo)致 CPU在 “系統(tǒng)態(tài) ”運(yùn)行劲妙,換而言之,雖然系統(tǒng)很忙碌儒喊,但是CPU用在 “用戶態(tài) ”的比例較小镣奋,應(yīng)用程序得不到充分的 CPU資源。
- 隨著CPU數(shù)目的增多怀愧,系統(tǒng)的性能反而下降侨颈。因?yàn)镃PU數(shù)目多余赢,同時(shí)運(yùn)行的線程就越多,可能就會(huì)造成更頻繁的線程上下文切換和系統(tǒng)態(tài)的CPU開銷哈垢,從而導(dǎo)致更糟糕的性能妻柒。
上面的描述,都是一個(gè) scalability(可擴(kuò)展性)很差的系統(tǒng)的表現(xiàn)耘分。從整體的性能指標(biāo)看举塔,由于線程熱鎖的存在,程序的響應(yīng)時(shí)間會(huì)變長(zhǎng)求泰,吞吐量會(huì)降低啤贩。
那么,怎么去了解 “熱鎖 ”出現(xiàn)在什么地方呢拜秧?
一個(gè)重要的方法是 結(jié)合操作系統(tǒng)的各種工具觀察系統(tǒng)資源使用狀況,以及收集Java線程的DUMP信息章郁,看線程都阻塞在什么方法上枉氮,了解原因,才能找到對(duì)應(yīng)的解決方法暖庄。
4 JVM重要線程
JVM運(yùn)行過(guò)程中產(chǎn)生的一些比較重要的線程羅列如下:
線程名稱 | 所屬 | 解釋說(shuō)明 |
---|---|---|
Attach Listener | JVM |
Attach Listener 線程是負(fù)責(zé)接收到外部的命令聊替,而對(duì)該命令進(jìn)行執(zhí)行的并把結(jié)果返回給發(fā)送者。通常我們會(huì)用一些命令去要求JVM給我們一些反饋信息培廓,如:java -version惹悄、jmap、jstack 等等肩钠。 如果該線程在JVM啟動(dòng)的時(shí)候沒(méi)有初始化泣港,那么,則會(huì)在用戶第一次執(zhí)行JVM命令時(shí)价匠,得到啟動(dòng)当纱。 |
Signal Dispatcher | JVM | 前面提到Attach Listener 線程的職責(zé)是接收外部JVM命令,當(dāng)命令接收成功后踩窖,會(huì)交給signal dispather 線程去進(jìn)行分發(fā)到各個(gè)不同的模塊處理命令坡氯,并且返回處理結(jié)果。signal dispather 線程也是在第一次接收外部JVM命令時(shí)洋腮,進(jìn)行初始化工作箫柳。 |
CompilerThread0 | JVM | 用來(lái)調(diào)用JITing ,實(shí)時(shí)編譯裝卸class 啥供。 通常悯恍,JVM會(huì)啟動(dòng)多個(gè)線程來(lái)處理這部分工作,線程名稱后面的數(shù)字也會(huì)累加滤灯,例如:CompilerThread1 坪稽。 |
Concurrent Mark-Sweep GC Thread | JVM | 并發(fā)標(biāo)記清除垃圾回收器(就是通常所說(shuō)的CMS GC)線程曼玩, 該線程主要針對(duì)于老年代垃圾回收。ps:?jiǎn)⒂迷摾厥掌髦习伲枰贘VM啟動(dòng)參數(shù)中加上:-XX:+UseConcMarkSweepGC 黍判。 |
DestroyJavaVM | JVM | 執(zhí)行main() 的線程,在main執(zhí)行完后調(diào)用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程篙梢,處于等待狀態(tài)顷帖,等待其它線程(Java線程和Native線程)退出時(shí)通知它卸載JVM。每個(gè)線程退出時(shí)渤滞,都會(huì)判斷自己當(dāng)前是否是整個(gè)JVM中最后一個(gè)非deamon線程 贬墩,如果是,則通知DestroyJavaVM 線程卸載JVM妄呕。 |
Finalizer Thread | JVM | 這個(gè)線程也是在main線程之后創(chuàng)建的陶舞,其優(yōu)先級(jí)為10,主要用于在垃圾收集前绪励,調(diào)用對(duì)象的finalize() 方法肿孵;關(guān)于Finalizer線程的幾點(diǎn):1) 只有當(dāng)開始一輪垃圾收集時(shí),才會(huì)開始調(diào)用finalize() 方法疏魏;因此并不是所有對(duì)象的finalize() 方法都會(huì)被執(zhí)行停做;2) 該線程也是daemon 線程,因此如果虛擬機(jī)中沒(méi)有其他非daemon線程大莫,不管該線程有沒(méi)有執(zhí)行完finalize() 方法蛉腌,JVM也會(huì)退出;3) JVM在垃圾收集時(shí)會(huì)將失去引用的對(duì)象包裝成Finalizer 對(duì)象(Reference 的實(shí)現(xiàn))只厘,并放入ReferenceQueue 烙丛,由Finalizer 線程來(lái)處理;最后將該Finalizer對(duì)象的引用置為null羔味,由垃圾收集器來(lái)回收蜀变;4) JVM為什么要單獨(dú)用一個(gè)線程來(lái)執(zhí)行finalize() 方法呢?如果JVM的垃圾收集線程自己來(lái)做介评,很有可能由于在finalize()方法中誤操作導(dǎo)致GC線程停止或不可控库北,這對(duì)GC線程來(lái)說(shuō)是一種災(zāi)難; |
Low Memory Detector | JVM | 這個(gè)線程是負(fù)責(zé)對(duì)可使用內(nèi)存進(jìn)行檢測(cè)们陆,如果發(fā)現(xiàn)可用內(nèi)存低寒瓦,分配新的內(nèi)存空間。 |
Reference Handler | JVM | JVM在創(chuàng)建main 線程后就創(chuàng)建Reference Handler 線程坪仇,其優(yōu)先級(jí)最高杂腰,為10,它主要用于處理引用對(duì)象本身(軟引用椅文、弱引用喂很、虛引用)的垃圾回收問(wèn)題 惜颇。 |
VM Thread | JVM | 這個(gè)線程就比較牛b了,是JVM里面的線程母體少辣,根據(jù)hotspot源碼(vmThread.hpp )里面的注釋凌摄,它是一個(gè)單個(gè)的對(duì)象(最原始的線程)會(huì)產(chǎn)生或觸發(fā)所有其他的線程,這個(gè)單個(gè)的VM線程是會(huì)被其他線程所使用來(lái)做一些VM操作(如:清掃垃圾等)漓帅。 |