一、介紹jstack是java虛擬機自帶的一種堆棧跟蹤工具。jstack用于打印出給定的java進程ID或core file或遠程調(diào)試服務的Java堆棧信息,如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式: jstack [-l] pid
主要分為兩個功能: a. 針對活著的進程做本地的或遠程的線程dump; b. 針對core文件做線程dump。 jstack用于生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因,如線程間死鎖、死循環(huán)、請求外部資源導致的長時間等待等。 線程出現(xiàn)停頓的時候通過jstack來查看各個線程的調(diào)用堆棧,就可以知道沒有響應的線程到底在后臺做什么事情,或者等待什么資源。 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現(xiàn)在運行的java程序呈現(xiàn)hung的狀態(tài),jstack是非常有用的。
線程狀態(tài)想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態(tài),下面這些狀態(tài)是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態(tài):
Monitor在多線程的 JAVA程序中,實現(xiàn)線程之間的同步,就要說說 Monitor。 Monitor是 Java中用以實現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下 面這個圖,描述了線程和 Monitor之間關系,以 及線程的狀態(tài)轉換圖: 進入?yún)^(qū)(Entrt Set):表示線程通過synchronized要求獲取對象的鎖。如果對象未被鎖住,則迚入擁有者;否則則在進入?yún)^(qū)等待。一旦對象鎖被其他線程釋放,立即參與競爭。 擁有者(The Owner):表示某一線程成功競爭到對象鎖。 等待區(qū)(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖,并在等待區(qū)等待被喚醒。 從圖中可以看出,一個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 synchronized(obj) { ......... } 調(diào)用修飾表示線程在方法調(diào)用時,額外的重要的操作。線程Dump分析的重要信息。修飾上方的方法調(dià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關鍵字,成功獲取到了對象的鎖,成為監(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關鍵字,沒有獲取到了對象的鎖,線程在監(jiā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關鍵字,成功獲取到了對象的鎖后,調(diào)用了wait方法,進入對象的等待區(qū)等待。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為WAITING或TIMED_WATING。 parking to wait for park是基本的線程阻塞原語,不通過監(jiān)視器在對象上阻塞。隨concurrent包會出現(xiàn)的新的機制,不synchronized體系不同。 線程動作線程狀態(tài)產(chǎn)生的原因
Wait on condition 該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生。具體是什么原因,可以結合 stacktrace來分析。 最常見的情況就是線程處于sleep狀態(tài),等待被喚醒。 常見的情況還有等待網(wǎng)絡IO:在java引入nio之前,對于每個網(wǎng)絡連接,都有一個對應的線程來處理網(wǎng)絡的讀寫操作,即使沒有可讀寫的數(shù)據(jù),線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統(tǒng)的線程調(diào)度也帶來壓力。在 NewIO里采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 正等待網(wǎng)絡讀寫,這可能是一個網(wǎng)絡瓶頸的征兆。因為網(wǎng)絡阻塞導致線程無法執(zhí)行。一種情況是網(wǎng)絡非常忙,幾 乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡讀 寫;另一種情況也可能是網(wǎng)絡空閑,但由于路由等問題,導致包無法正常的到達。所以要結合系統(tǒng)的一些性能觀察工具來綜合分析,比如 netstat統(tǒng)計單位時間的發(fā)送包的數(shù)目,如果很明顯超過了所在網(wǎng)絡帶寬的限制 ; 觀察 cpu的利用率,如果系統(tǒng)態(tài)的 CPU時間,相對于用戶態(tài)的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上,可以用 dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運行時間遙遙領先;這些都指向由于網(wǎng)絡帶寬所限導致的網(wǎng)絡瓶頸。(來自http://www./jzone/articles/303979.html) 二、命令格式jstack [ option ] pid
常用參數(shù)說明 1)options: executable Java executable from which the core dump was produced.(可能是產(chǎn)生core dump的java可執(zhí)行程序) core 將被打印信息的core dump文件 remote-hostname-or-IP 遠程debug服務的主機名或ip server-id 唯一id,假如一臺主機上多個遠程debug服務 2)基本參數(shù): -F當’jstack [-l] pid’沒有相應的時候強制打印棧信息,如果直接jstack無響應時,用于強制jstack),一般情況不需要使用 -l長列表. 打印關于鎖的附加信息,例如屬于java.util.concurrent的ownable synchronizers列表,會使得JVM停頓得長久得多(可能會差很多倍,比如普通的jstack可能幾毫秒和一次GC沒區(qū)別,加了-l 就是近一秒的時間),-l 建議不要用。一般情況不需要使用 -m打印java和native c/c++框架的所有棧信息.可以打印JVM的堆棧,顯示上Native的棧幀,一般應用排查不需要使用 -h | -help打印幫助信息 pid 需要被打印配置信息的java進程id,可以用jps查詢.
線程dump的分析工具:
三、使用實例1、jstack pid ~$ jps -ml org.apache.catalina.startup.Bootstrap ~$ jstack 5661 2013-04-16 21:09:27 Full thread dump Java HotSpot(TM) Server VM (20.10-b01 mixed mode): "Attach Listener" daemon prio=10 tid=0x70e95400 nid=0x2265 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "http-bio-8080-exec-20" daemon prio=10 tid=0x08a35800 nid=0x1d42 waiting on condition [0x70997000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x766a27b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:947) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907) at java.lang.Thread.run(Thread.java:662) ........
#jstack -l 4089 >1.txt,查看1.txt內(nèi)容如下所示: 2014-03-14 10:47:04 Full thread dump Java HotSpot(TM) Client VM (20.45-b01 mixed mode, sharing): "Attach Listener" daemon prio=10 tid=0x08251400 nid=0x11bd runnable [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "DestroyJavaVM" prio=10 tid=0xb3a0a800 nid=0xffa waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "Query Listener" prio=10 tid=0xb3a09800 nid=0x1023 runnable [0xb3b72000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408) - locked <0x70a84430> (a java.net.SocksSocketImpl) at java.net.ServerSocket.implAccept(ServerSocket.java:462) at java.net.ServerSocket.accept(ServerSocket.java:430) at com.sun.tools.hat.internal.server.QueryListener.waitForRequests(QueryListener.java:76) at com.sun.tools.hat.internal.server.QueryListener.run(QueryListener.java:65) at java.lang.Thread.run(Thread.java:662) Locked ownable synchronizers: - None "Low Memory Detector" daemon prio=10 tid=0x08220400 nid=0x1000 runnable [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "C1 CompilerThread0" daemon prio=10 tid=0x08214c00 nid=0xfff waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "Signal Dispatcher" daemon prio=10 tid=0x08213000 nid=0xffe runnable [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None "Finalizer" daemon prio=10 tid=0x0820bc00 nid=0xffd in Object.wait() [0xb5075000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x7a2b6f50> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118) - locked <0x7a2b6f50> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:171) Locked ownable synchronizers: - None "Reference Handler" daemon prio=10 tid=0x0820a400 nid=0xffc in Object.wait() [0xb50c7000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x7a2b6fe0> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:485) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116) - locked <0x7a2b6fe0> (a java.lang.ref.Reference$Lock) Locked ownable synchronizers: - None "VM Thread" prio=10 tid=0x08200000 nid=0xffb runnable "VM Periodic Task Thread" prio=10 tid=0x08222400 nid=0x1001 waiting on condition JNI global references: 1317
一般情況下,通過jstack輸出的線程信息主要包括:jvm自身線程、用戶線程等。其中jvm線程會在jvm啟動時就會存在。對于用戶線程則是在用戶訪問時才會生成。
2、jstack 查看線程具體在做什么,可看出哪些線程在長時間占用CPU,盡快定位問題和解決問題 http://www./topic/1114219 1.top查找出哪個進程消耗的cpu高。執(zhí)行top命令,默認是進程視圖,其中PID是進程號
21125 co_ad2 18 0 1817m 776m 9712 S 3.3 4.9 12:03.24 java 5284 co_ad 21 0 3028m 2.5g 9432 S 1.0 16.3 6629:44 ja 這里我們分析21125這個java進程 2.top中shift+h 或“H”查找出哪個線程消耗的cpu高 先輸入top,然后再按shift+h 或“H”,此時打開的是線程視圖,pid為線程號 21233 co_ad2 15 0 1807m 630m 9492 S 1.3 4.0 0:05.12 java 20503 co_ad2_s 15 0 1360m 560m 9176 S 0.3 3.6 0:46.72 java 這里我們分析21233這個線程,并且注意的是,這個線程是屬于21125這個進程的。 3.使用jstack命令輸出這一時刻的線程棧,保存到文件,命名為jstack.log。注意:輸出線程棧和保存top命令快照盡量同時進行。
3、代碼示例 運行代碼: /** * @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 ...此處省略若干內(nèi)容... "main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000] java.lang.Thread.State: RUNNABLE at javaCommand.JStackDemo1.main(JStackDemo1.java:7) 我們可以從這段堆棧信息中看出什么來呢?我們可以看到,當前一共有一條用戶級別線程,線程處于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{ @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) 我們能看到:
為什么同時鎖住的等待同一個資源:
四、如何分析1、線程Dump的分析原則
入手點進入?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ù)運行的IO IO操作是可以以RUNNABLE狀態(tài)達成阻塞。例如:數(shù)據(jù)庫死鎖、網(wǎng)絡讀寫。 格外注意對IO線程的真實狀態(tài)的分析。 一般來說,被捕捉到RUNNABLE的IO調(diào)用,都是有問題的。 以下堆棧顯示: 線程狀態(tài)為RUNNABLE。 調(diào)用棧在SocketInputStream或SocketImpl上,socketRead0等方法。 調(diào)用棧包含了jdbc相關的包。很可能發(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() 入手點總結wait on monitor entry: 被阻塞的,肯定有問題 runnable : 注意IO線程 in Object.wait(): 注意非線程池等待
2、死鎖分析學會了怎么使用jstack命令之后,我們就可以看看,如何使用jstack分析死鎖了,這也是我們一定要掌握的內(nèi)容。 啥叫死鎖? 所謂死鎖: 是指兩個或兩個以上的進程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎ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(); } 當我啟動該程序時,我們看一下控制臺: 我們發(fā)現(xiàn),程序只輸出了兩行內(nèi)容,然后程序就不再打印其它的東西了,但是程序并沒有停止。這樣就產(chǎn)生了死鎖。 當線程1使用 然后,我們使用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. 哈哈,堆棧寫的很明顯,它告訴我們
其他虛擬機執(zhí)行Full GC時,會阻塞所有的用戶線程。因此,即時獲取到同步鎖的線程也有可能被阻塞。 在查看線程Dump時,首先查看內(nèi)存使用情況。
對于jstack做的ThreadDump的棧,可以反映如下信息(源自):
jstack -m 會打印出JVM堆棧信息,涉及C、C++部分代碼,可能需要配合gdb命令來分析。
頻繁GC問題或內(nèi)存溢出問題一、使用 二、使用 三、使用 四、使用 五、使用jhat或者可視化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)分析堆情況。 六、結合代碼解決內(nèi)存溢出或泄露問題。
死鎖問題一、使用 二、使用
主要參考資料:
|
|
來自: liang1234_ > 《性能調(diào)優(yōu)》