Java命令学习系列(二)——Jstack
轉(zhuǎn)載自?Java命令學(xué)習(xí)系列(二)——Jstack
jstack是java虛擬機(jī)自帶的一種堆棧跟蹤工具。
功能
jstack用于生成java虛擬機(jī)當(dāng)前時刻的線程快照。線程快照是當(dāng)前java虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因,如線程間死鎖、死循環(huán)、請求外部資源導(dǎo)致的長時間等待等。 線程出現(xiàn)停頓的時候通過jstack來查看各個線程的調(diào)用堆棧,就可以知道沒有響應(yīng)的線程到底在后臺做什么事情,或者等待什么資源。 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題。另外,jstack工具還可以附屬到正在運(yùn)行的java程序中,看到當(dāng)時運(yùn)行的java程序的java stack和native stack的信息, 如果現(xiàn)在運(yùn)行的java程序呈現(xiàn)hung的狀態(tài),jstack是非常有用的。
So,jstack命令主要用來查看Java線程的調(diào)用堆棧的,可以用來分析線程問題(如死鎖)。
線程狀態(tài)
想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態(tài),下面這些狀態(tài)是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態(tài):
NEW,未啟動的。不會出現(xiàn)在Dump中。
RUNNABLE,在虛擬機(jī)內(nèi)執(zhí)行的。
BLOCKED,受阻塞并等待監(jiān)視器鎖。
WATING,無限期等待另一個線程執(zhí)行特定操作。
TIMED_WATING,有時限的等待另一個線程的特定操作。
TERMINATED,已退出的。
Monitor
在多線程的 JAVA程序中,實(shí)現(xiàn)線程之間的同步,就要說說 Monitor。?Monitor是 Java中用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下 面這個圖,描述了線程和 Monitor之間關(guān)系,以 及線程的狀態(tài)轉(zhuǎn)換圖:
進(jìn)入?yún)^(qū)(Entrt Set):表示線程通過synchronized要求獲取對象的鎖。如果對象未被鎖住,則迚入擁有者;否則則在進(jìn)入?yún)^(qū)等待。一旦對象鎖被其他線程釋放,立即參與競爭。
擁有者(The Owner):表示某一線程成功競爭到對象鎖。
等待區(qū)(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖,并在等待區(qū)等待被喚醒。
從圖中可以看出,一個 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保護(hù)起來的代碼段為臨界區(qū)。當(dāng)一個線程申請進(jìn)入臨界區(qū)時,它就進(jìn)入了 “Entry Set”隊列。對應(yīng)的 code就像:
synchronized(obj) { .........}調(diào)用修飾
表示線程在方法調(diào)用時,額外的重要的操作。線程Dump分析的重要信息。修飾上方的方法調(diào)用。
locked <地址> 目標(biāo):使用synchronized申請對象鎖成功,監(jiān)視器的擁有者。
waiting to lock <地址> 目標(biāo):使用synchronized申請對象鎖未成功,在迚入?yún)^(qū)等待。
waiting on <地址> 目標(biāo):使用synchronized申請對象鎖成功后,釋放鎖幵在等待區(qū)等待。
parking to wait for <地址> 目標(biāo)
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關(guān)鍵字,成功獲取到了對象的鎖,成為監(jiān)視器的擁有者,在臨界區(qū)內(nèi)操作。對象鎖是可以線程重入的。
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關(guān)鍵字,沒有獲取到了對象的鎖,線程在監(jiān)視器的進(jìn)入?yún)^(qū)等待。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為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關(guān)鍵字,成功獲取到了對象的鎖后,調(diào)用了wait方法,進(jìn)入對象的等待區(qū)等待。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為WAITING或TIMED_WATING。
parking to wait for
park是基本的線程阻塞原語,不通過監(jiān)視器在對象上阻塞。隨concurrent包會出現(xiàn)的新的機(jī)制,不synchronized體系不同。
線程動作
線程狀態(tài)產(chǎn)生的原因
runnable:狀態(tài)一般為RUNNABLE。
in Object.wait():等待區(qū)等待,狀態(tài)為WAITING或TIMED_WAITING。
waiting for monitor entry:進(jìn)入?yún)^(qū)等待,狀態(tài)為BLOCKED。
waiting on condition:等待區(qū)等待、被park。
sleeping:休眠的線程,調(diào)用了Thread.sleep()。
Wait on condition?該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生。具體是什么原因,可以結(jié)合 stacktrace來分析。 最常見的情況就是線程處于sleep狀態(tài),等待被喚醒。 常見的情況還有等待網(wǎng)絡(luò)IO:在java引入nio之前,對于每個網(wǎng)絡(luò)連接,都有一個對應(yīng)的線程來處理網(wǎng)絡(luò)的讀寫操作,即使沒有可讀寫的數(shù)據(jù),線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費(fèi),而且給操作系統(tǒng)的線程調(diào)度也帶來壓力。在 NewIO里采用了新的機(jī)制,編寫的服務(wù)器程序的性能和可擴(kuò)展性都得到提高。 正等待網(wǎng)絡(luò)讀寫,這可能是一個網(wǎng)絡(luò)瓶頸的征兆。因?yàn)榫W(wǎng)絡(luò)阻塞導(dǎo)致線程無法執(zhí)行。一種情況是網(wǎng)絡(luò)非常忙,幾 乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡(luò)讀 寫;另一種情況也可能是網(wǎng)絡(luò)空閑,但由于路由等問題,導(dǎo)致包無法正常的到達(dá)。所以要結(jié)合系統(tǒng)的一些性能觀察工具來綜合分析,比如 netstat統(tǒng)計單位時間的發(fā)送包的數(shù)目,如果很明顯超過了所在網(wǎng)絡(luò)帶寬的限制 ; 觀察 cpu的利用率,如果系統(tǒng)態(tài)的 CPU時間,相對于用戶態(tài)的 CPU時間比例較高;如果程序運(yùn)行在 Solaris 10平臺上,可以用 dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運(yùn)行時間遙遙領(lǐng)先;這些都指向由于網(wǎng)絡(luò)帶寬所限導(dǎo)致的網(wǎng)絡(luò)瓶頸。(來自http://www.blogjava.net/jzone/articles/303979.html)
線程Dump的分析
原則
結(jié)合代碼閱讀的推理。需要線程Dump和源碼的相互推導(dǎo)和印證。
造成Bug的根源往往丌會在調(diào)用棧上直接體現(xiàn),一定格外注意線程當(dāng)前調(diào)用之前的所有調(diào)用。
入手點(diǎn)
進(jìn)入?yún)^(qū)等待
"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()線程狀態(tài)BLOCKED,線程動作wait on monitor entry,調(diào)用修飾waiting to lock總是一起出現(xiàn)。表示在代碼級別已經(jīng)存在沖突的調(diào)用。必然有問題的代碼,需要盡可能減少其發(fā)生。
同步塊阻塞
一個線程鎖住某對象,大量其他線程在該對象上等待。
"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)持續(xù)運(yùn)行的IO?IO操作是可以以RUNNABLE狀態(tài)達(dá)成阻塞。例如:數(shù)據(jù)庫死鎖、網(wǎng)絡(luò)讀寫。 格外注意對IO線程的真實(shí)狀態(tài)的分析。 一般來說,被捕捉到RUNNABLE的IO調(diào)用,都是有問題的。
以下堆棧顯示: 線程狀態(tài)為RUNNABLE。 調(diào)用棧在SocketInputStream或SocketImpl上,socketRead0等方法。 調(diào)用棧包含了jdbc相關(guān)的包。很可能發(fā)生了數(shù)據(jù)庫死鎖
"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)分線程調(diào)度的休眠
正常的線程池等待
"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()入手點(diǎn)總結(jié)
wait on monitor entry:?被阻塞的,肯定有問題
runnable?: 注意IO線程
in Object.wait(): 注意非線程池等待
使用
想要學(xué)習(xí)一個命令,先來看看幫助,使用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當(dāng)’jstack [-l] pid’沒有相應(yīng)的時候強(qiáng)制打印棧信息?-l長列表. 打印關(guān)于鎖的附加信息,例如屬于java.util.concurrent的ownable synchronizers列表.?-m打印java和native c/c++框架的所有棧信息.?-h?| -help打印幫助信息?pid?需要被打印配置信息的java進(jìn)程id,可以用jps查詢.
首先,我們分析這么一段程序的線程情況:
/*** @author hollis*/ public class JStackDemo1 {public static void main(String[] args) {while (true) {//Do Nothing}} }先是有jps查看進(jìn)程號:
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 ...此處省略若干內(nèi)容... "main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000]java.lang.Thread.State: RUNNABLEat javaCommand.JStackDemo1.main(JStackDemo1.java:7)我們可以從這段堆棧信息中看出什么來呢?我們可以看到,當(dāng)前一共有一條用戶級別線程,線程處于runnable狀態(tài),執(zhí)行到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{@Overridepublic 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)我們能看到:
線程的狀態(tài): WAITING 線程的調(diào)用棧 線程的當(dāng)前鎖住的資源: <0x0000000783e066e0> 線程當(dāng)前等待的資源:<0x0000000783e066e0>
為什么同時鎖住的等待同一個資源:
線程的執(zhí)行中,先獲得了這個對象的 Monitor(對應(yīng)于 locked <0x0000000783e066e0>)。當(dāng)執(zhí)行到 obj.wait(), 線程即放棄了 Monitor的所有權(quán),進(jìn)入 “wait set”隊列(對應(yīng)于 waiting on <0x0000000783e066e0> )。
死鎖分析
學(xué)會了怎么使用jstack命令之后,我們就可以看看,如何使用jstack分析死鎖了,這也是我們一定要掌握的內(nèi)容。?啥叫死鎖??所謂死鎖: 是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。 說白了,我現(xiàn)在想吃雞蛋灌餅,桌子上放著雞蛋和餅,但是我和我的朋友同時分別拿起了雞蛋和病,我手里拿著雞蛋,但是我需要他手里的餅。他手里拿著餅,但是他想要我手里的雞蛋。就這樣,如果不能同時拿到雞蛋和餅,那我們就不能繼續(xù)做后面的工作(做雞蛋灌餅)。所以,這就造成了死鎖。?看一段死鎖的程序:
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則調(diào)用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則調(diào)用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(); }當(dāng)我啟動該程序時,我們看一下控制臺:
我們發(fā)現(xiàn),程序只輸出了兩行內(nèi)容,然后程序就不再打印其它的東西了,但是程序并沒有停止。這樣就產(chǎn)生了死鎖。 當(dāng)線程1使用synchronized鎖住了o1的同時,線程2也是用synchronized鎖住了o2。當(dāng)兩個線程都執(zhí)行完第一個打印任務(wù)的時候,線程1想鎖住o2,線程2想鎖住o1。但是,線程1當(dāng)前鎖著o1,線程2鎖著o2。所以兩個想成都無法繼續(xù)執(zhí)行下去,就造成了死鎖。
然后,我們使用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,然后指出造成死鎖的兩個線程的內(nèi)容。然后,又通過?Java stack information for the threads listed above來顯示更詳細(xì)的死鎖的信息。 他說
Thread-1在想要執(zhí)行第40行的時候,當(dāng)前鎖住了資源<0x00000007d6aa2ca8>,但是他在等待資源<0x00000007d6aa2c98>?Thread-0在想要執(zhí)行第27行的時候,當(dāng)前鎖住了資源<0x00000007d6aa2c98>,但是他在等待資源<0x00000007d6aa2ca8>?由于這兩個線程都持有資源,并且都需要對方的資源,所以造成了死鎖。 原因我們找到了,就可以具體問題具體分析,解決這個死鎖了。
其他
虛擬機(jī)執(zhí)行Full GC時,會阻塞所有的用戶線程。因此,即時獲取到同步鎖的線程也有可能被阻塞。?在查看線程Dump時,首先查看內(nèi)存使用情況。
總結(jié)
以上是生活随笔為你收集整理的Java命令学习系列(二)——Jstack的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 《EA Sports FC Tactic
- 下一篇: 专门玩原神的台式电脑?
