1. 程式人生 > >Java命令學習系列:Jstack

Java命令學習系列:Jstack

內存 時間 tar 多線程 通過 javac ESS reference force

jstack是java虛擬機自帶的一種堆棧跟蹤工具。

功能

jstack用於生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。 線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做什麽事情,或者等待什麽資源。 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。

So,jstack命令主要用來查看Java線程的調用堆棧的,可以用來分析線程問題(如死鎖)。

線程狀態

想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態,下面這些狀態是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態:

NEW,未啟動的。不會出現在Dump中。

RUNNABLE,在虛擬機內執行的。

BLOCKED,受阻塞並等待監視器鎖。

WATING,無限期等待另一個線程執行特定操作。

TIMED_WATING,有時限的等待另一個線程的特定操作。

TERMINATED,已退出的。

Monitor

在多線程的 JAVA程序中,實現線程之間的同步,就要說說 Monitor。 Monitor是 Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下 面這個圖,描述了線程和 Monitor之間關系,以 及線程的狀態轉換圖:

技術分享圖片

進入區(Entrt Set):表示線程通過synchronized要求獲取對象的鎖。如果對象未被鎖住,則迚入擁有者;否則則在進入區等待。一旦對象鎖被其他線程釋放,立即參與競爭。

擁有者(The Owner):表示某一線程成功競爭到對象鎖。

等待區(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖,並在等待區等待被喚醒。

從圖中可以看出,一個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”

“Wait Set”裏面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態是 “in Object.wait()”。 先看 “Entry Set”裏面的線程。我們稱被 synchronized保護起來的代碼段為臨界區。當一個線程申請進入臨界區時,它就進入了 “Entry Set”隊列。對應的 code就像:

synchronized(obj) {
 ......... 
}

調用修飾

表示線程在方法調用時,額外的重要的操作。線程Dump分析的重要信息。修飾上方的方法調用。

locked <地址> 目標:使用synchronized申請對象鎖成功,監視器的擁有者。

waiting to lock <地址> 目標:使用synchronized申請對象鎖未成功,在迚入區等待。

waiting on <地址> 目標:使用synchronized申請對象鎖成功後,釋放鎖幵在等待區等待。

parking to wait for <地址> 目標

locked

1 at oracle.jdbc.driver.PhysicalConnection.prepareStatement
2 - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
3 at oracle.jdbc.driver.PhysicalConnection.prepareStatement
4 - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
5 at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement

通過synchronized關鍵字,成功獲取到了對象的鎖,成為監視器的擁有者,在臨界區內操作。對象鎖是可以線程重入的。

waiting to lock

1 at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
2 - waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
3 at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
4 at com.jiuqi.dna.core.impl.ContextImpl.find
5 at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo

通過synchronized關鍵字,沒有獲取到了對象的鎖,線程在監視器的進入區等待。在調用棧頂出現,線程狀態為Blocked。

waiting on

1 at java.lang.Object.wait(Native Method)
2 - waiting on <0x00000000da2defb0> (a WorkingThread)
3 at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
4 - locked <0x00000000da2defb0> (a WorkingThread)
5 at com.jiuqi.dna.core.impl.WorkingThread.run

通過synchronized關鍵字,成功獲取到了對象的鎖後,調用了wait方法,進入對象的等待區等待。在調用棧頂出現,線程狀態為WAITING或TIMED_WATING。

parking to wait for

park是基本的線程阻塞原語,不通過監視器在對象上阻塞。隨concurrent包會出現的新的機制,不synchronized體系不同。

線程動作

線程狀態產生的原因

runnable:狀態一般為RUNNABLE。

in Object.wait():等待區等待,狀態為WAITING或TIMED_WAITING。

waiting for monitor entry:進入區等待,狀態為BLOCKED。

waiting on condition:等待區等待、被park。

sleeping:休眠的線程,調用了Thread.sleep()。

Wait on condition 該狀態出現在線程等待某個條件的發生。具體是什麽原因,可以結合 stacktrace來分析。 最常見的情況就是線程處於sleep狀態,等待被喚醒。 常見的情況還有等待網絡IO:在java引入nio之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NewIO裏采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 正等待網絡讀寫,這可能是一個網絡瓶頸的征兆。因為網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾 乎消耗了所有的帶寬,仍然有大量數據等待網絡讀 寫;另一種情況也可能是網絡空閑,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。(來自http://www.blogjava.net/jzone/articles/303979.html)

線程Dump的分析

原則

結合代碼閱讀的推理。需要線程Dump和源碼的相互推導和印證。

造成Bug的根源往往丌會在調用棧上直接體現,一定格外註意線程當前調用之前的所有調用。

入手點

進入區等待

1 "d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000]
2 java.lang.Thread.State: BLOCKED (on object monitor)
3 at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
4 - waiting to lock <0x0000000602f38e90> (a java.lang.Object)
5 at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()

線程狀態BLOCKED,線程動作wait on monitor entry,調用修飾waiting to lock總是一起出現。表示在代碼級別已經存在沖突的調用。必然有問題的代碼,需要盡可能減少其發生。

同步塊阻塞

一個線程鎖住某對象,大量其他線程在該對象上等待。

 1 "blocker" runnable
 2 java.lang.Thread.State: RUNNABLE
 3 at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23)
 4 - locked <0x00000000eb8eff68> (a java.lang.Object)
 5 "blockee-11" waiting for monitor entry
 6 java.lang.Thread.State: BLOCKED (on object monitor)
 7 at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
 8 - waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
 9 "blockee-86" waiting for monitor entry
10 java.lang.Thread.State: BLOCKED (on object monitor)
11 at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
12 - waiting to lock <0x00000000eb8eff68> (a java.lang.Object)

持續運行的IO IO操作是可以以RUNNABLE狀態達成阻塞。例如:數據庫死鎖、網絡讀寫。 格外註意對IO線程的真實狀態的分析。 一般來說,被捕捉到RUNNABLE的IO調用,都是有問題的。

以下堆棧顯示: 線程狀態為RUNNABLE。 調用棧在SocketInputStream或SocketImpl上,socketRead0等方法。 調用棧包含了jdbc相關的包。很可能發生了數據庫死鎖

 1 "d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable
 2 [0x0000000027cbd000]
 3 java.lang.Thread.State: RUNNABLE
 4 at java.net.SocketInputStream.socketRead0(Native Method)
 5 at java.net.SocketInputStream.read(Unknown Source)
 6 at oracle.net.ns.Packet.receive(Packet.java:240)
 7 at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
 8 at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172)
 9 at oracle.net.ns.NetInputStream.read(NetInputStream.java:117)
10 at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034)
11 at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)

分線程調度的休眠

正常的線程池等待

1 "d&a-131" in Object.wait()
2 java.lang.Thread.State: TIMED_WAITING (on object monitor)
3 at java.lang.Object.wait(Native Method)
4 at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322)
5 - locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread)
6 at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)

可疑的線程等待

1 "d&a-121" in Object.wait()
2 java.lang.Thread.State: WAITING (on object monitor)
3 at java.lang.Object.wait(Native Method)
4 at java.lang.Object.wait(Object.java:485)
5 at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive()
6 - locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup)
7 at com.jiuqi.dna.core.impl.Transaction.lock()

入手點總結

wait on monitor entry: 被阻塞的,肯定有問題

runnable : 註意IO線程

in Object.wait(): 註意非線程池等待

使用

想要學習一個命令,先來看看幫助,使用jstack -help查看幫助:

 1 [email protected]:~$ jstack -help
 2 Usage:
 3     jstack [-l] <pid>
 4         (to connect to running process)
 5     jstack -F [-m] [-l] <pid>
 6         (to connect to a hung process)
 7     jstack [-m] [-l] <executable> <core>
 8         (to connect to a core file)
 9     jstack [-m] [-l] [[email protected]]<remote server IP or hostname>
10         (to connect to a remote debug server)
11  
12 Options:
13     -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
14     -m  to print both java and native frames (mixed mode)
15     -l  long listing. Prints additional information about locks
16     -h or -help to print this help message

-F當’jstack [-l] pid’沒有相應的時候強制打印棧信息 -l長列表. 打印關於鎖的附加信息,例如屬於java.util.concurrent的ownable synchronizers列表. -m打印java和native c/c++框架的所有棧信息. -h | -help打印幫助信息 pid 需要被打印配置信息的java進程id,可以用jps查詢.

首先,我們分析這麽一段程序的線程情況:

 1 /**
 2  * @author hollis
 3  */
 4 public class JStackDemo1 {
 5     public static void main(String[] args) {
 6         while (true) {
 7             //Do Nothing
 8         }
 9     }
10 }

先是有jps查看進程號:

1 [email protected]:~$ jps
2 29788 JStackDemo1
3 29834 Jps
4 22385 org.e clipse.equinox.launcher_1.3.0.v20130327-1440.jar

然後使用jstack 查看堆棧信息:

1 [email protected]:~$ jstack 29788
2 2015-04-17 23:47:31
3 ...此處省略若幹內容...
4 "main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000]
5    java.lang.Thread.State: RUNNABLE
6     at javaCommand.JStackDemo1.main(JStackDemo1.java:7)

我們可以從這段堆棧信息中看出什麽來呢?我們可以看到,當前一共有一條用戶級別線程,線程處於runnable狀態,執行到JStackDemo1.java的第七行。 看下面代碼:

 1 /**
 2  * @author hollis
 3  */
 4 public class JStackDemo1 {
 5     public static void main(String[] args) {
 6         Thread thread = new Thread(new Thread1());
 7         thread.start();
 8     }
 9 }
10 class Thread1 implements Runnable{
11     @Override
12     public void run() {
13         while(true){
14             System.out.println(1);
15         }
16     }
17 }

線程堆棧信息如下:

1 "Reference Handler" daemon prio=10 tid=0x00007fbbcc06e000 nid=0x286c in Object.wait() [0x00007fbbc8dfc000]
2    java.lang.Thread.State: WAITING (on object monitor)
3     at java.lang.Object.wait(Native Method)
4     - waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)
5     at java.lang.Object.wait(Object.java:503)
6     at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
7     - locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)

我們能看到:

線程的狀態: WAITING 線程的調用棧 線程的當前鎖住的資源: <0x0000000783e066e0> 線程當前等待的資源:<0x0000000783e066e0>

為什麽同時鎖住的等待同一個資源:

線程的執行中,先獲得了這個對象的 Monitor(對應於 locked <0x0000000783e066e0>)。當執行到 obj.wait(), 線程即放棄了 Monitor的所有權,進入 “wait set”隊列(對應於 waiting on <0x0000000783e066e0> )。

死鎖分析

學會了怎麽使用jstack命令之後,我們就可以看看,如何使用jstack分析死鎖了,這也是我們一定要掌握的內容。 啥叫死鎖? 所謂死鎖: 是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。 說白了,我現在想吃雞蛋灌餅,桌子上放著雞蛋和餅,但是我和我的朋友同時分別拿起了雞蛋和病,我手裏拿著雞蛋,但是我需要他手裏的餅。他手裏拿著餅,但是他想要我手裏的雞蛋。就這樣,如果不能同時拿到雞蛋和餅,那我們就不能繼續做後面的工作(做雞蛋灌餅)。所以,這就造成了死鎖。 看一段死鎖的程序

 1 package javaCommand;
 2 /**
 3  * @author hollis
 4  */
 5 public class JStackDemo {
 6     public static void main(String[] args) {
 7         Thread t1 = new Thread(new DeadLockclass(true));//建立一個線程
 8         Thread t2 = new Thread(new DeadLockclass(false));//建立另一個線程
 9         t1.start();//啟動一個線程
10         t2.start();//啟動另一個線程
11     }
12 }
13 class DeadLockclass implements Runnable {
14     public boolean falg;// 控制線程
15     DeadLockclass(boolean falg) {
16         this.falg = falg;
17     }
18     public void run() {
19         /**
20          * 如果falg的值為true則調用t1線程
21          */
22         if (falg) {
23             while (true) {
24                 synchronized (Suo.o1) {
25                     System.out.println("o1 " + Thread.currentThread().getName());
26                     synchronized (Suo.o2) {
27                         System.out.println("o2 " + Thread.currentThread().getName());
28                     }
29                 }
30             }
31         }
32         /**
33          * 如果falg的值為false則調用t2線程
34          */
35         else {
36             while (true) {
37                 synchronized (Suo.o2) {
38                     System.out.println("o2 " + Thread.currentThread().getName());
39                     synchronized (Suo.o1) {
40                         System.out.println("o1 " + Thread.currentThread().getName());
41                     }
42                 }
43             }
44         }
45     }
46 }
47  
48 class Suo {
49     static Object o1 = new Object();
50     static Object o2 = new Object();
51 }

當我啟動該程序時,我們看一下控制臺:

技術分享圖片

我們發現,程序只輸出了兩行內容,然後程序就不再打印其它的東西了,但是程序並沒有停止。這樣就產生了死鎖。 當線程1使用synchronized鎖住了o1的同時,線程2也是用synchronized鎖住了o2。當兩個線程都執行完第一個打印任務的時候,線程1想鎖住o2,線程2想鎖住o1。但是,線程1當前鎖著o1,線程2鎖著o2。所以兩個想成都無法繼續執行下去,就造成了死鎖。

然後,我們使用jstack來看一下線程堆棧信息:

 1 Found one Java-level deadlock:
 2 =============================
 3 "Thread-1":
 4   waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object),
 5   which is held by "Thread-0"
 6 "Thread-0":
 7   waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object),
 8   which is held by "Thread-1"
 9 
10 Java stack information for the threads listed above:
11 ===================================================
12 "Thread-1":
13     at javaCommand.DeadLockclass.run(JStackDemo.java:40)
14     - waiting to lock <0x00000007d6aa2c98> (a java.lang.Object)
15     - locked <0x00000007d6aa2ca8> (a java.lang.Object)
16     at java.lang.Thread.run(Thread.java:745)
17 "Thread-0":
18     at javaCommand.DeadLockclass.run(JStackDemo.java:27)
19     - waiting to lock <0x00000007d6aa2ca8> (a java.lang.Object)
20     - locked <0x00000007d6aa2c98> (a java.lang.Object)
21     at java.lang.Thread.run(Thread.java:745)
22 
23 Found 1 deadlock.

哈哈,堆棧寫的很明顯,它告訴我們 Found one Java-level deadlock,然後指出造成死鎖的兩個線程的內容。然後,又通過 Java stack information for the threads listed above來顯示更詳細的死鎖的信息。 他說

Thread-1在想要執行第40行的時候,當前鎖住了資源<0x00000007d6aa2ca8>,但是他在等待資源<0x00000007d6aa2c98> Thread-0在想要執行第27行的時候,當前鎖住了資源<0x00000007d6aa2c98>,但是他在等待資源<0x00000007d6aa2ca8> 由於這兩個線程都持有資源,並且都需要對方的資源,所以造成了死鎖。 原因我們找到了,就可以具體問題具體分析,解決這個死鎖了。

其他

虛擬機執行Full GC時,會阻塞所有的用戶線程。因此,即時獲取到同步鎖的線程也有可能被阻塞。 在查看線程Dump時,首先查看內存使用情況。

Java命令學習系列:Jstack