您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何進行ThreadDump問題分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
隨著系統日益增大,代碼結構日漸復雜,經過驗收測試的系統可能在實際生產環境下表現的一塌糊涂,也可能非常棒。因此通過QA并不能保證系統不會發生內存泄漏,往往流量越大泄露的越快,最后導致系統崩潰。比如在某個時間點系統一直出現TimeOut、或者系統突然處理速度急劇下降等問題。對于開發人員就非常棘手了,很多人根本一頭霧水,基本上就是拍腦袋瞎猜了。其實發現此類問題定位的技術主要有內存泄漏定位、線程堆棧分析等。
線程堆棧也就是所謂的線程調用棧(都是獨立的),在Java線程堆棧式JVM線程狀態的一個瞬時快照,快照包含了當前時刻所有線程的運行狀態,包括每一個線程的調用棧,鎖的持有等信息。每個虛擬機都提供了Thread Dump的后門幫助我們導出堆棧信息。借助線程堆棧會幫助我們迅速地縮小問題的范圍, 找到突破口, 命中目標 。
線程堆棧一般包含的信息: 1、線程名字,ID,線程的數量 2、線程運行狀態、鎖的狀態(鎖被哪個線程持有,哪個線程在等待) 3、函數調用層級關系。包括了完整類名,方法名,源代碼行數
線程堆棧信息的多少,依賴于你系統的復雜度,借助堆棧信息,可分析很多問題,如線程死鎖、鎖競爭、死循環、識別耗時操作等。在多線程場景下進行穩定問題分析和性能分析他是最有效的方法,多數情況甚至無需了解系統就可以進行相應分析。
當然,線程堆棧也不是萬能的,因為其是瞬時快照,所以對已消失沒有留下痕跡的信息,是無法追蹤歷史的。這種情況只能借助于監控系統和日志進行分析。如連接池中的連接被哪些線程使用了沒有被釋放這類問題。其實也就是說線程堆棧分析的都是非功能性的問題。
線程堆棧善于分析以下問題: 1、系統無緣無故CPU過高 2、系統掛起,無響應 3、系統運行越來越慢 4、線程死鎖、死循環、餓死等。 5、由于線程數量太多導致系統失敗(如無法創建線程等)
輸出堆棧 1、unix/Linux 使用kill -3 pid 進行轉儲 2、可以使用VisualVM等直接查看轉儲 3、JDK1.5以上的可以使用Thread.getStackTrace() 控制堆棧自動打印
只有實際操作了你才能更加的理解其概念,上一個DEMO
/** * Created by QIANG on 2017/9/29 */ public class ThreadTest { Object obj1 = new Object(); Object obj2 = new Object(); public void fun1(){ synchronized (obj1){ fun2(); } } public void fun2(){ synchronized (obj2){ while (true){ System.out.println("我要一直跑跑跑。。。"); } } } public static void main(String[] args){ ThreadTest aa = new ThreadTest(); aa.fun1(); } }
這里我是用VisualVM 打印線程堆棧,在完整的堆棧信息中我們可以看到各種系統線程,限于篇幅原因只留下我關注的信息
2017-10-19 10:46:09 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode): "main" #1 prio=5 os_prio=0 tid=0x0000000002737000 nid=0x5d1c runnable [0x000000000270f000] java.lang.Thread.State: RUNNABLE at java.io.FileOutputStream.writeBytes(Native Method) at java.io.FileOutputStream.write(FileOutputStream.java:326) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) - locked <0x0000000081e1c728> (a java.io.BufferedOutputStream) at java.io.PrintStream.write(PrintStream.java:482) - locked <0x0000000081e1c0a0> (a java.io.PrintStream) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104) - locked <0x0000000081e1c058> (a java.io.OutputStreamWriter) at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185) at java.io.PrintStream.write(PrintStream.java:527) - eliminated <0x0000000081e1c0a0> (a java.io.PrintStream) at java.io.PrintStream.print(PrintStream.java:669) at java.io.PrintStream.println(PrintStream.java:806) - locked <0x0000000081e1c0a0> (a java.io.PrintStream) at com.ecej.test.Thread.ThreadTest.fun2(ThreadTest.java:21) - locked <0x0000000081e0a300> (a java.lang.Object) at com.ecej.test.Thread.ThreadTest.fun1(ThreadTest.java:14) - locked <0x0000000081e1c0c0> (a java.lang.Object) at com.ecej.test.Thread.ThreadTest.main(ThreadTest.java:28) Locked ownable synchronizers: - None "VM Thread" os_prio=2 tid=0x0000000017928800 nid=0x3160 runnable "VM Periodic Task Thread" os_prio=2 tid=0x000000001a2d8000 nid=0x3dac waiting on condition
線程堆棧是從下往上看,借助這些信息當前系統做了什么就一目了然了。從main線程堆棧看出, - locked <0x0000000081e1c0c0> (a java.lang.Object) 這句代表該線程已經占有鎖,鎖ID為0x0000000081e1c0c0,這個ID是系統自動生成的,只需要知道每次打印堆棧同一ID表示同一個鎖就行了。
分析下堆棧第一行信息 "main" (線程名字)#1 prio=5(線程優先級) os_prio=0 tid=0x0000000002737000(線程ID) nid=0x5d1c(線程對應本地線程ID) runnable(線程狀態) [0x000000000270f000](線程占用內存地址)
nid=0x5d1c(線程對應本地線程ID) 這個大家看的有點懵逼,因為java我們開啟一個線程就必然對應JVM中一個本地線程,也就是說本地線程數和java線程堆棧線程數相同。 在linux下我們可以使用以下方式:
使用ps -ef | grep java 獲得Java進程ID。
使用pstack <java pid>獲得Java虛擬機的本地線程的堆棧6。
如獲得Thread 8 (Thread 4067802000 (LWP 3356)),其中Thread 4067802000 (LWP 3356)都是表示為本地線程ID。這里LWP 就對應了ThreadDump中的nid(native thread id ),只是nid用的是16進制表示的。分析得知,java線程和本地線程是同一個東西,本地線程才是真正的線程實體。
繼續分析標識,其中“runable”表示當前線程處于運行狀態。這個runable標識只能表示在JVM中此線程處于運行狀態,熟悉線程的同學一定知道,此狀態不代表真的在消耗PU。處于Runable的線程只能說明該線程沒有被阻塞在java的wait\sleep上,也沒有等待在鎖上。但如果該線程調用了本地方法,而本地方法處于等待狀態,那JVM是不知道本地代碼中發生了什么,盡管當前線程實際處于阻塞狀態,但實際顯示出來的還是runable狀態,這種情況不消耗CPU。看個例子:
java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method)
在實際中socket的本地方法大多數時間是阻塞的,除非緩沖區有數據。因此我們在分析哪個線程在消耗大量CPU時, 不能以這個"runnable"字樣作為判斷該線程是否消耗CPU的依據。 我們常在線程堆棧中發現".init"或者".clinit"字樣的函數, 比如下面得信息 at java.util.logging.LogManager.<clinit>
(LogManager.java:156)
“.<clinit>
”表示當前正在執行類的初始化。“<init>
”正在執行對象得構造函數。下面說下兩種初始化:
類初始化:編譯器把類的變量初始化語句和類型得靜態初始化器都收集到cllinit方法內,只能由JVM調用并保證線程安全。再初始化前必須保證其超類已經被初始化。
并非所有的類都會擁有一個<clinit>
() 方法, 在以下條件中該類不會擁有<client>
() 方法: ? 該類既沒有聲明任何類變量, 也沒有靜態初始化語句; ? 該類聲明了類變量, 但沒有明確使用類變量初始化語句或靜態初始化語句初始化; ? 該類僅包含靜態final 變量的類變量初始化語句, 并且類變量初始化語句是編譯時常量表達式
說白話一點就是類是初始化靜態變量的,final static 直接進常量池了,不在此步驟。這一階段其實就是類加載的最后一階段。對象的初始化是代表了此對象生命周期的開始,只是調用了構造方法包括其里面的內容。
在進行堆棧解讀前再了解下鎖的基本常識,大家都知道wait()\sleep()的重大區別吧。相同點就是都釋放CPU,不同點就是wait()釋放鎖而sleep()不釋放。
從上邊例子的堆棧信息可以看出鎖的重要信息: 1、當一個線程占有一個鎖時,線程堆棧會打印 - locked <0x0000000081e06858> (a java.io.InputStreamReader) 2、當一個線程正在等待其它線程釋放該鎖, 線程堆棧中會打印—waiting to lock <0x0000000081e06858> 3、當一個線程占有一個鎖, 但又執行到該鎖的wait()上, 線程堆棧中首先打印locked,然后又會打印—waiting on <0x0000000081e06858>
從解讀中可以看出鎖有三個重要的特征字:locked,waiting to lock,waiting on,了解這三個特征字, 就能夠對鎖進行分析了 。大多數是有一個 -—waiting to lock就必然有一個locked,當然有時候你也會找不到locked,因為一個線程釋放鎖和另一個線程被喚醒有一個空窗期,這時候轉儲就會發生此種情況。
借助堆棧可以分析很多類型問題,CPU消耗分析是堆棧的一個重要內容,先看下java的線程流轉圖(圖來源于網絡)
下面說下堆棧中的線程狀態
RUNABLE 從JVM來說,此狀態就是線程正在運行,但是咧,實際上他不一定消耗CPU,有可能是正在進行網絡IO,此時線程大多數是被掛起的,只有當數據到達后,線程才會重新被喚醒,掛起發生在本地代碼(Native)中,JVM其實根本就不知道(可不是調用wait/sleep)。
來個例子,下面的堆棧表示了當前程序正在從網絡讀取數據。
Thread-39" daemon prio=1 tid=0x08646590 nid=0x666d runnable [5beb7000..5beb88b8] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method)
下面這個堆棧表示了線程實實在在的在消耗CPU
"Thread-444" prio=1 tid=0xa4853568 nid=0x7ade runnable [0xafcf7000..0xafcf8680] java.lang.Thread.State: RUNNABLE at org.apache.commons.collections.ReferenceMap.getEntry(Unknown Source) at org.apache.commons.collections.ReferenceMap.get(Unknown Source)
TIMED_WAITING(on object monitor) 表示當前線程被掛起一段時間,正在執行obj.wait(int time)方法
"JMX server connection timeout 16" #16 daemon prio=5 os_prio=0 tid=0x000000001a59f000 nid=0x5a74 in Object.wait() [0x000000001b7df000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000081f218e0> (a [I) at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168) - locked <0x0000000081f218e0> (a [I) at java.lang.Thread.run(Thread.java:748)
TIMED_WAITING(sleeping) 表示當前線程被掛起一段時間,即正在執行Thread.sleep(intt ime)方法
"Comm thread" daemon prio=10 tid=0x00002aaad4107400 nid=0x649f waiting on condition [0x000000004133b000..0x000000004133ba00] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at org.apache.hadoop.mapred.Task$1.run(Task.java:282) at java.lang.Thread.run(Thread.java:619)
TIMED_WAITING(parking) 當前線程被掛起一段時間,即正在執行Thread.sleep(int time)方法 ,就是執行本地方法的sleep
"RMI TCP" daemon prio=6 tid=0x0ae3b800 nid=0x958 waiting on condition [0x17eff000..0x17effa94] java.lang.Thread.State: TIMED_WAITING (parking )
WAINTING(on object monitor) 當前線程被掛起, 即正在執行obj.wait()方法 (無參數的wait()方法)
"IPC Client" daemon prio=10 tid=0x00002aaad4129800 nid=0x649d in Object.wait() [0x039000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method)
SO,處于TIMED_WAITING、 WAINTING狀態的線程一定不消耗CPU. 處于RUNNABLE的線程, 要結合當前線程代碼的性質判斷, 是否消耗CPU. ? 如果是純Java運算代碼, 則消耗CPU. ? 如果是網絡IO,很少消耗CPU. ? 如果是本地代碼, 結合本地代碼的性質判斷(可以通過pstack/gstack獲取本地線程堆棧),如果是純運算代碼, 則消耗CPU, 如果被掛起, 則不消耗CPU,如果是IO,則不怎么消耗CPU。
看完上述內容,你們對如何進行ThreadDump問題分析有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。