☆啃碎并發(fā)(四):Java線程Dump分析

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)

  1. 能在各種操作系統(tǒng)下使用;
  2. 能在各種Java應(yīng)用服務(wù)器下使用咬腋;
  3. 能在生產(chǎn)環(huán)境下使用而不影響系統(tǒng)的性能羹膳;
  4. 能將問(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)題的典型性。

  1. 操作系統(tǒng)命令獲取ThreadDump

    1. ps –ef | grep java
    2. kill -3 <pid>

    注意:

    一定要謹(jǐn)慎, 一步不慎就可能讓服務(wù)器進(jìn)程被殺死铲球。kill -9 命令會(huì)殺死進(jìn)程沟优。

  2. JVM 自帶的工具獲取線程堆棧

    1. jps 或 ps –ef | grep java (獲取PID)
    2. jstack [-l ] <pid> | tee -a jstack.log(獲取ThreadDump)

2 Thread Dump分析

2.1 Thread Dump信息

  1. 頭部信息:時(shí)間,JVM信息

    2011-11-02 19:05:06  
    Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode): 
    
  2. 線程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)題根源痢站。

  3. 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;  
}
  1. 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ì)象饲漾。

  2. 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)題)败玉。

  3. BLOCKED

    線程正在等待獲取java對(duì)象的監(jiān)視器(也叫內(nèi)置鎖),即線程正在等待進(jìn)入由synchronized保護(hù)的方法或者代碼塊镜硕。synchronized用來(lái)保證原子性运翼,任意時(shí)刻最多只能由一個(gè)線程進(jìn)入該臨界區(qū)域,其他線程只能排隊(duì)等待兴枯。

  4. WAITING

    處在該線程的狀態(tài)血淌,正在等待某個(gè)事件的發(fā)生,只有特定的條件滿足财剖,才能獲得執(zhí)行機(jī)會(huì)悠夯。而產(chǎn)生這個(gè)特定的事件,通常都是另一個(gè)線程躺坟。也就是說(shuō)沦补,如果不發(fā)生特定的事件,那么處在該狀態(tài)的線程一直等待咪橙,不能獲取執(zhí)行的機(jī)會(huì)夕膀。比如:

    1. A線程調(diào)用了obj對(duì)象的obj.wait()方法,如果沒(méi)有線程調(diào)用obj.notify或obj.notifyAll美侦,那么A線程就沒(méi)有辦法恢復(fù)運(yùn)行产舞;
    2. 如果A線程調(diào)用了LockSupport.park(),沒(méi)有別的線程調(diào)用LockSupport.unpark(A)菠剩,那么A沒(méi)有辦法恢復(fù)運(yùn)行易猫。
  5. 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)行

  6. TERMINATED

    線程執(zhí)行完畢泞遗,執(zhí)行完run方法正常返回旷太,或者拋出了運(yùn)行時(shí)異常而結(jié)束,線程都會(huì)停留在這個(gè)狀態(tài)批旺。這個(gè)時(shí)候線程只剩下Thread對(duì)象了幌陕,沒(méi)有什么用了。

2.3 關(guān)鍵狀態(tài)分析

  1. Wait on conditionThe 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)大致為以下幾種:

    1. java.lang.Thread.State: WAITING (parking):一直等那個(gè)條件發(fā)生暇赤;
    2. java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時(shí)的心例,那個(gè)條件不到來(lái),也將定時(shí)喚醒自己鞋囊。
  2. 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í)有兩種可能性:

    1. 該 monitor不被其它線程擁有典蝌, Entry Set里面也沒(méi)有其它等待線程。本線程即成為相應(yīng)類或者對(duì)象的 Monitor的 Owner头谜,執(zhí)行臨界區(qū)的代碼骏掀。
    2. 該 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的線程社证。

  3. 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)示例

  1. 顯示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ū)域梁沧。

  2. 顯示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í)踐下。

  3. 顯示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)景

  1. CPU飆高施敢,load高周荐,響應(yīng)很慢
  1. 一個(gè)請(qǐng)求過(guò)程中多次dump
  2. 對(duì)比多次dump文件的runnable線程僵娃,如果執(zhí)行的方法有比較大變化概作,說(shuō)明比較正常。如果在執(zhí)行同一個(gè)方法默怨,就有一些問(wèn)題了讯榕;
  1. 查找占用CPU最多的線程
  1. 使用命令: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)制垃僚;
  2. 在thread dump中集绰,根據(jù)top命令查找的線程id,查找對(duì)應(yīng)的線程堆棧信息谆棺;
  1. CPU使用率不高但是響應(yīng)很慢

進(jìn)行dump栽燕,查看是否有很多thread struck在了i/o罕袋、數(shù)據(jù)庫(kù)等地方,定位瓶頸原因碍岔;

  1. 請(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):

  1. 頻繁的線程的上下文切換:從操作系統(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ì)列中染坯。
  2. 大量的系統(tǒng)調(diào)用:因?yàn)榫€程的上下文切換均芽,以及熱鎖的競(jìng)爭(zhēng),或者臨界區(qū)的頻繁的進(jìn)出单鹿,都可能導(dǎo)致大量的系統(tǒng)調(diào)用掀宋。
  3. 大部分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資源。
  4. 隨著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操作(如:清掃垃圾等)漓帅。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锨亏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子忙干,更是在濱河造成了極大的恐慌器予,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捐迫,死亡現(xiàn)場(chǎng)離奇詭異乾翔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)施戴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門末融,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人暇韧,你說(shuō)我怎么就攤上這事∨ǖ桑” “怎么了懈玻?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乾颁。 經(jīng)常有香客問(wèn)我涂乌,道長(zhǎng),這世上最難降的妖魔是什么英岭? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任湾盒,我火速辦了婚禮,結(jié)果婚禮上诅妹,老公的妹妹穿的比我還像新娘罚勾。我一直安慰自己,他們只是感情好吭狡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布尖殃。 她就那樣靜靜地躺著,像睡著了一般划煮。 火紅的嫁衣襯著肌膚如雪送丰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天弛秋,我揣著相機(jī)與錄音器躏,去河邊找鬼俐载。 笑死,一個(gè)胖子當(dāng)著我的面吹牛登失,可吹牛的內(nèi)容都是我干的遏佣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壁畸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贼急!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捏萍,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤太抓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后令杈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體走敌,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年逗噩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掉丽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡异雁,死狀恐怖捶障,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纲刀,我是刑警寧澤项炼,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站示绊,受9級(jí)特大地震影響锭部,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜面褐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一拌禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧展哭,春花似錦湃窍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至析恢,卻和暖如春墨坚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工泽篮, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盗尸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓帽撑,卻偏偏與公主長(zhǎng)得像泼各,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亏拉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Thread Dump介紹 什么是Thread Dump Thread Dump是非常有用的診斷Java應(yīng)用問(wèn)題的...
    薩達(dá)哈魯醬閱讀 1,012評(píng)論 0 2
  • jstack用于打印出給定的java進(jìn)程ID或core file或遠(yuǎn)程調(diào)試服務(wù)的Java堆棧信息扣蜻,如果是在64位機(jī)...
    sherlock_6981閱讀 944評(píng)論 0 0
  • http://jameswxx.iteye.com/blog/1041173 jstack命令的語(yǔ)法格式: jst...
    sherlock_6981閱讀 526評(píng)論 0 1
  • 姓名 連嘉瑋 學(xué)號(hào) 16040120089 轉(zhuǎn)自:http://www.reibang.com/p/9426e68...
    連嘉瑋閱讀 618評(píng)論 0 2
  • 了解線程由來(lái) 單核CPU之所以能夠?qū)崿F(xiàn)多進(jìn)程,主要是依賴操作系統(tǒng)的進(jìn)程調(diào)度算法及塘。如時(shí)間片輪轉(zhuǎn)算法莽使,可以實(shí)現(xiàn)QQ、微...
    yuesf閱讀 696評(píng)論 0 0