jstack是java虛擬機自帶的一種堆棧跟蹤工具。html
jstack用於生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的緣由,如線程間死鎖、死循環、請求外部資源致使的長時間等待等。 線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情,或者等待什麼資源。 若是java程序崩潰生成core文件,jstack工具能夠用來得到core文件的java stack和native stack的信息,從而能夠輕鬆地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還能夠附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 若是如今運行的java程序呈現hung的狀態,jstack是很是有用的。java
So,jstack命令主要用來查看Java線程的調用堆棧的,能夠用來分析線程問題(如死鎖)。c++
想要經過jstack命令來分析線程的狀況的話,首先要知道線程都有哪些狀態,下面這些狀態是咱們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態:數據庫
NEW,未啓動的。不會出如今Dump中。服務器
RUNNABLE,在虛擬機內執行的。網絡
BLOCKED,受阻塞並等待監視器鎖。多線程
WATING,無限期等待另外一個線程執行特定操做。oracle
TIMED_WATING,有時限的等待另外一個線程的特定操做。框架
TERMINATED,已退出的。eclipse
在多線程的 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
at oracle.jdbc.driver.PhysicalConnection.prepareStatement - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection) at oracle.jdbc.driver.PhysicalConnection.prepareStatement - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection) at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement
經過synchronized關鍵字,成功獲取到了對象的鎖,成爲監視器的擁有者,在臨界區內操做。對象鎖是能夠線程重入的。
waiting to lock
at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165) - waiting to lock <0x0000000097ba9aa8> (a CacheHolder) at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder at com.jiuqi.dna.core.impl.ContextImpl.find at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo
經過synchronized關鍵字,沒有獲取到了對象的鎖,線程在監視器的進入區等待。在調用棧頂出現,線程狀態爲Blocked。
waiting on
at java.lang.Object.wait(Native Method) - waiting on <0x00000000da2defb0> (a WorkingThread) at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo - locked <0x00000000da2defb0> (a WorkingThread) 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和源碼的相互推導和印證。
形成Bug的根源每每丌會在調用棧上直接體現,必定格外注意線程當前調用以前的全部調用。
進入區等待
"d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000] java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle() - waiting to lock <0x0000000602f38e90> (a java.lang.Object) at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
線程狀態BLOCKED,線程動做wait on monitor entry,調用修飾waiting to lock老是一塊兒出現。表示在代碼級別已經存在衝突的調用。必然有問題的代碼,須要儘量減小其發生。
同步塊阻塞
一個線程鎖住某對象,大量其餘線程在該對象上等待。
"blocker" runnable java.lang.Thread.State: RUNNABLE at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23) - locked <0x00000000eb8eff68> (a java.lang.Object) "blockee-11" waiting for monitor entry java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41) - waiting to lock <0x00000000eb8eff68> (a java.lang.Object) "blockee-86" waiting for monitor entry java.lang.Thread.State: BLOCKED (on object monitor) at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41) - waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
持續運行的IO IO操做是能夠以RUNNABLE狀態達成阻塞。例如:數據庫死鎖、網絡讀寫。 格外注意對IO線程的真實狀態的分析。 通常來講,被捕捉到RUNNABLE的IO調用,都是有問題的。
如下堆棧顯示: 線程狀態爲RUNNABLE。 調用棧在SocketInputStream或SocketImpl上,socketRead0等方法。 調用棧包含了jdbc相關的包。極可能發生了數據庫死鎖
"d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable [0x0000000027cbd000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(Unknown Source) at oracle.net.ns.Packet.receive(Packet.java:240) at oracle.net.ns.DataPacket.receive(DataPacket.java:92) at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172) at oracle.net.ns.NetInputStream.read(NetInputStream.java:117) at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034) at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)
分線程調度的休眠
正常的線程池等待
"d&a-131" in Object.wait() java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322) - locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread) at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)
可疑的線程等待
"d&a-121" in Object.wait() java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:485) at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive() - locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup) at com.jiuqi.dna.core.impl.Transaction.lock()
wait on monitor entry: 被阻塞的,確定有問題
runnable : 注意IO線程
in Object.wait(): 注意非線程池等待
想要學習一個命令,先來看看幫助,使用jstack -help查看幫助:
hollis@hos:~$ jstack -help Usage: jstack [-l] <pid> (to connect to running process) jstack -F [-m] [-l] <pid> (to connect to a hung process) jstack [-m] [-l] <executable> <core> (to connect to a core file) jstack [-m] [-l] [server_id@]<remote server IP or hostname> (to connect to a remote debug server) Options: -F to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -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查詢.
首先,咱們分析這麼一段程序的線程狀況:
/** * @author hollis */ public class JStackDemo1 { public static void main(String[] args) { while (true) { //Do Nothing } } }
先是有jps查看進程號:
hollis@hos:~$ jps 29788 JStackDemo1 29834 Jps 22385 org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar
而後使用jstack 查看堆棧信息:
hollis@hos:~$ jstack 29788 2015-04-17 23:47:31 ...此處省略若干內容... "main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000] java.lang.Thread.State: RUNNABLE at javaCommand.JStackDemo1.main(JStackDemo1.java:7)
咱們能夠從這段堆棧信息中看出什麼來呢?咱們能夠看到,當前一共有一條用戶級別線程,線程處於runnable狀態,執行到JStackDemo1.java的第七行。 看下面代碼:
/** * @author hollis */ public class JStackDemo1 { public static void main(String[] args) { Thread thread = new Thread(new Thread1()); thread.start(); } } class Thread1 implements Runnable{ @Override public void run() { while(true){ System.out.println(1); } } }
線程堆棧信息以下:
"Reference Handler" daemon prio=10 tid=0x00007fbbcc06e000 nid=0x286c in Object.wait() [0x00007fbbc8dfc000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)
咱們能看到:
線程的狀態: WAITING 線程的調用棧 線程的當前鎖住的資源: <0x0000000783e066e0> 線程當前等待的資源:<0x0000000783e066e0>
爲何同時鎖住的等待同一個資源:
線程的執行中,先得到了這個對象的 Monitor(對應於 locked <0x0000000783e066e0>)。當執行到 obj.wait(), 線程即放棄了 Monitor的全部權,進入 「wait set」隊列(對應於 waiting on <0x0000000783e066e0> )。
學會了怎麼使用jstack命令以後,咱們就能夠看看,如何使用jstack分析死鎖了,這也是咱們必定要掌握的內容。 啥叫死鎖? 所謂死鎖: 是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。 說白了,我如今想吃雞蛋灌餅,桌子上放着雞蛋和餅,可是我和個人朋友同時分別拿起了雞蛋和病,我手裏拿着雞蛋,可是我須要他手裏的餅。他手裏拿着餅,可是他想要我手裏的雞蛋。就這樣,若是不能同時拿到雞蛋和餅,那咱們就不能繼續作後面的工做(作雞蛋灌餅)。因此,這就形成了死鎖。 看一段死鎖的程序:
package javaCommand; /** * @author hollis */ public class JStackDemo { public static void main(String[] args) { Thread t1 = new Thread(new DeadLockclass(true));//創建一個線程 Thread t2 = new Thread(new DeadLockclass(false));//創建另外一個線程 t1.start();//啓動一個線程 t2.start();//啓動另外一個線程 } } class DeadLockclass implements Runnable { public boolean falg;// 控制線程 DeadLockclass(boolean falg) { this.falg = falg; } public void run() { /** * 若是falg的值爲true則調用t1線程 */ if (falg) { while (true) { synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); } } } } /** * 若是falg的值爲false則調用t2線程 */ else { while (true) { synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); } } } } } } class Suo { static Object o1 = new Object(); static Object o2 = new Object(); }
當我啓動該程序時,咱們看一下控制檯:
咱們發現,程序只輸出了兩行內容,而後程序就再也不打印其它的東西了,可是程序並無中止。這樣就產生了死鎖。 當線程1使用synchronized
鎖住了o1的同時,線程2也是用synchronized
鎖住了o2。當兩個線程都執行完第一個打印任務的時候,線程1想鎖住o2,線程2想鎖住o1。可是,線程1當前鎖着o1,線程2鎖着o2。因此兩個想成都沒法繼續執行下去,就形成了死鎖。
而後,咱們使用jstack來看一下線程堆棧信息:
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at javaCommand.DeadLockclass.run(JStackDemo.java:40) - waiting to lock <0x00000007d6aa2c98> (a java.lang.Object) - locked <0x00000007d6aa2ca8> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) "Thread-0": at javaCommand.DeadLockclass.run(JStackDemo.java:27) - waiting to lock <0x00000007d6aa2ca8> (a java.lang.Object) - locked <0x00000007d6aa2c98> (a java.lang.Object) at java.lang.Thread.run(Thread.java:745) 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時,首先查看內存使用狀況。