Java線程堆棧分析

姓名 連嘉瑋 學號 16040120089

轉自:http://www.lxweimin.com/p/9426e682c173?utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=qq

有刪節

【嵌牛導讀】:線程堆棧應該是多線程類應用程序非功能問題定位的最有效手段,可以說是殺手锏。

【嵌牛鼻子】:系統運行與響應

【嵌牛提問】:如何解決這些類型的問題?

【嵌牛正文】:

寫在前面: 線程堆棧應該是多線程類應用程序非功能問題定位的最有效手段,可以說是殺手锏。線程堆棧最擅長與分析如下類型問題:

系統無緣無故CPU過高。

系統掛起,無響應。

系統運行越來越慢。

性能瓶頸(如無法充分利用CPU等)

線程死鎖、死循環,餓死等。

由于線程數量太多導致系統失敗(如無法創建線程等)。

如何解讀線程堆棧

如下面一段Java源代碼程序:

package org.ccgogoing.study.stacktrace;

/**

* @Author: LuoChong400

* @Description: 測試線程

* @Date: Create in 07:27 PM 2017/12/08

*/

public class MyTest {

? ? ? ? Object obj1 = new Object();

? ? ? ? Object obj2 = new Object();

? ? ? ? public void fun1() {

? ? ? ? ? ? synchronized (obj1) {

? ? ? ? ? ? ? ? fun2();

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? public void fun2() {

? ? ? ? ? ? synchronized (obj2) {

? ? ? ? ? ? ? ? while (true) { //為了打印堆棧,該函數堆棧分析不退出

? ? ? ? ? ? ? ? ? ? System.out.print("");

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? public static void main(String[] args) {

? ? ? ? ? ? MyTest aa = new MyTest();

? ? ? ? ? ? aa.fun1();

? ? ? ? }

? ? }

在Idea 中運行該程序,然后按下CTRL+BREAK鍵,打印出線程堆棧信息如下:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode):

"Service Thread" daemon prio=6 tid=0x000000000c53b000 nid=0xca58 runnable [0x0000000000000000]

? java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=10 tid=0x000000000c516000 nid=0xd390 waiting on condition [0x0000000000000000]

? java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=10 tid=0x000000000c515000 nid=0xcbac waiting on condition [0x0000000000000000]

? java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" daemon prio=6 tid=0x000000000c514000 nid=0xd148 runnable [0x000000000caee000]

? java.lang.Thread.State: RUNNABLE

? ? at java.net.SocketInputStream.socketRead0(Native Method)

? ? at java.net.SocketInputStream.read(SocketInputStream.java:152)

? ? at java.net.SocketInputStream.read(SocketInputStream.java:122)

? ? at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:283)

? ? at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:325)

? ? at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:177)

? ? - locked <0x00000000d7858b50> (a java.io.InputStreamReader)

? ? at java.io.InputStreamReader.read(InputStreamReader.java:184)

? ? at java.io.BufferedReader.fill(BufferedReader.java:154)

? ? at java.io.BufferedReader.readLine(BufferedReader.java:317)

? ? - locked <0x00000000d7858b50> (a java.io.InputStreamReader)

? ? at java.io.BufferedReader.readLine(BufferedReader.java:382)

? ? at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" daemon prio=10 tid=0x000000000ad4a000 nid=0xd24c runnable [0x0000000000000000]

? java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x000000000c1a8800 nid=0xd200 waiting on condition [0x0000000000000000]

? java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=0x000000000ace6000 nid=0xcd74 in Object.wait() [0x000000000c13f000]

? java.lang.Thread.State: WAITING (on object monitor)

? ? at java.lang.Object.wait(Native Method)

? ? - waiting on <0x00000000d7284858> (a java.lang.ref.ReferenceQueue$Lock)

? ? at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)

? ? - locked <0x00000000d7284858> (a java.lang.ref.ReferenceQueue$Lock)

? ? at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)

? ? at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" daemon prio=10 tid=0x000000000ace4800 nid=0xce34 in Object.wait() [0x000000000bf4f000]

? java.lang.Thread.State: WAITING (on object monitor)

? ? at java.lang.Object.wait(Native Method)

? ? - waiting on <0x00000000d7284470> (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 <0x00000000d7284470> (a java.lang.ref.Reference$Lock)

"main" prio=6 tid=0x000000000238e800 nid=0xc940 runnable [0x00000000027af000]

? java.lang.Thread.State: RUNNABLE

? ? at org.ccgogoing.study.stacktrace.MyTest.fun2(MyTest.java:22)

? ? - locked <0x00000000d77d50c8> (a java.lang.Object)

? ? at org.ccgogoing.study.stacktrace.MyTest.fun1(MyTest.java:15)

? ? - locked <0x00000000d77d50b8> (a java.lang.Object)

? ? at org.ccgogoing.study.stacktrace.MyTest.main(MyTest.java:29)

"VM Thread" prio=10 tid=0x000000000ace1000 nid=0xd0a8 runnable

"GC task thread#0 (ParallelGC)" prio=6 tid=0x00000000023a4000 nid=0xd398 runnable

"GC task thread#1 (ParallelGC)" prio=6 tid=0x00000000023a5800 nid=0xcc20 runnable

"GC task thread#2 (ParallelGC)" prio=6 tid=0x00000000023a7000 nid=0xb914 runnable

"GC task thread#3 (ParallelGC)" prio=6 tid=0x00000000023a9000 nid=0xd088 runnable

"VM Periodic Task Thread" prio=10 tid=0x000000000c53f000 nid=0xc1b4 waiting on condition

JNI global references: 138

Heap

PSYoungGen? ? ? total 36864K, used 6376K [0x00000000d7280000, 0x00000000d9b80000, 0x0000000100000000)

? eden space 31744K, 20% used [0x00000000d7280000,0x00000000d78ba0d0,0x00000000d9180000)

? from space 5120K, 0% used [0x00000000d9680000,0x00000000d9680000,0x00000000d9b80000)

? to? space 5120K, 0% used [0x00000000d9180000,0x00000000d9180000,0x00000000d9680000)

ParOldGen? ? ? total 83456K, used 0K [0x0000000085800000, 0x000000008a980000, 0x00000000d7280000)

? object space 83456K, 0% used [0x0000000085800000,0x0000000085800000,0x000000008a980000)

PSPermGen? ? ? total 21504K, used 3300K [0x0000000080600000, 0x0000000081b00000, 0x0000000085800000)

? object space 21504K, 15% used [0x0000000080600000,0x0000000080939290,0x0000000081b00000)

在上面這段堆棧輸出中,可以看到有很多后臺線程和main線程,其中只有main線程屬于Java用戶線程,其他幾個都是虛擬機自動創建的,我們分析的過程中,只關心用戶線程即可。

從上面的main線程中可以很直觀的看到當前線程的調用上下文,其中一個線程的某一層調用含義如下:

at MyTest.fun1(MyTest.java:15)

? ? |? ? |? ? |? ? ? ? ? ? ? |

? ? |? ? |? ? |? ? ? ? ? ? ? +-----當前正在調用的函數所在的源代碼文件的行號

? ? |? ? |? ? +------------當前正在調用的函數所在的源代碼文件

? ? |? ? +---------------------當前正在調用的方法名

? ? +---------------------------當前正在調用的類名

另外,堆棧中有:- locked <0x00000000d77d50b8> (a java.lang.Object)語句,表示該線程已經占有柯鎖<0x00000000d77d50b8>,尖括號中表示鎖ID,這個事系統自動產生的,我們只需要知道每次打印的堆棧,同一個ID表示是同一個鎖即可。每一個線程堆棧的第一行含義如下:

"main" prio=1 tid=0x000000000238e800 nid=0xc940 runnable [0x00000000027af000]

? ? |? ? ? |? |? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? |? ? ? ? ? |

? ? |? ? ? |? |? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? |? ? ? ? ? +--線程占用內存地址

? ? |? ? ? |? |? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? +-----------線程的狀態

? ? |? ? ? |? |? ? ? ? ? ? ? ? ? ? ? +----線程對應的本地線程id號

? ? |? ? ? |? +-------------------線程id

? ? |? ? ? +--------------------------線程優先級

? ? +-------------------------------線程名稱

? ?

其中需要說明的是,線程對應的本地線程id號,是指Java線程所對應的虛擬機中的本地線程。由于Java是解析型語言,執行的實體是Java虛擬機,因此Java語言中的線程是依附于虛擬機中的本地線程來運行的,實際上是本地線程在執行Java線程代碼。

鎖的解讀

從上面的線程堆棧看,線程堆棧中包含的直接信息為:線程的個數,每個線程調用的方法堆棧,當前鎖的狀態。線程的個數可以直接數出來;線程調用的方法堆棧,從下向上看,即表示當前的線程調用了哪個類上的哪個方法。而鎖得狀態看起來稍微有一點技巧。與鎖相關的信息如下:

當一個線程占有一個鎖的時候,線程的堆棧中會打印--locked<0x00000000d77d50c8>

當一個線程正在等待其它線程釋放該鎖,線程堆棧中會打印--waiting to lock<0x00000000d77d50c8>

當一個線程占有一個鎖,但又執行到該鎖的wait()方法上,線程堆棧中首先打印locked,然后又會打印--waiting on <0x00000000d77d50c8>

線程狀態的解讀

借助線程堆棧,可以分析很多類型的問題,CPU的消耗分析即是線程堆棧分析的一個重要內容;

處于TIMED_WAITING、WAITING狀態的線程一定不消耗CPU。處于RUNNABLE的線程,要結合當前代碼的性質判斷,是否消耗CPU。

如果是純Java運算代碼,則消耗CPU。

如果是網絡IO,很少消耗CPU。

如果是本地代碼,要結合本地代碼的性質判斷(可以通過pstack、gstack獲取本地線程堆棧),如果是純運算代碼,則消耗CPU,如果被掛起,則不消耗CPU,如果是IO,則不怎么消耗CPU。

如何借助線程堆棧分析問題

線程堆棧在定位如下類型的問題上非常有幫助:

線程死鎖的分析

Java代碼導致的CPU過高分析

死循環分析

資源不足分析

性能瓶頸分析

線程死鎖分析

死鎖的概念就不做過多解釋了,不明白的可以去網上查查;

兩個或超過兩個線程因為環路的鎖依賴關系而形成的鎖環,就形成了真正的死鎖,如下為死鎖喉打印的堆棧:

Found one Java-level deadlock:

=============================

"org.ccgogoing.study.stacktrace.deadlock.TestThread2":

? waiting to lock monitor 0x000000000a9ad118 (object 0x00000000d77363d0, a java.lang.Object),

? which is held by "org.ccgogoing.study.stacktrace.deadlock.TestThread1"

"org.ccgogoing.study.stacktrace.deadlock.TestThread1":

? waiting to lock monitor 0x000000000a9abc78 (object 0x00000000d77363e0, a java.lang.Object),

? which is held by "org.ccgogoing.study.stacktrace.deadlock.TestThread2"

Java stack information for the threads listed above:

===================================================

"org.ccgogoing.study.stacktrace.deadlock.TestThread2":

? ? at org.ccgogoing.study.stacktrace.deadlock.TestThread2.fun(TestThread2.java:35)

? ? - waiting to lock <0x00000000d77363d0> (a java.lang.Object)

? ? - locked <0x00000000d77363e0> (a java.lang.Object)

? ? at org.ccgogoing.study.stacktrace.deadlock.TestThread2.run(TestThread2.java:22)

"org.ccgogoing.study.stacktrace.deadlock.TestThread1":

? ? at org.ccgogoing.study.stacktrace.deadlock.TestThread1.fun(TestThread1.java:33)

? ? - waiting to lock <0x00000000d77363e0> (a java.lang.Object)

? ? - locked <0x00000000d77363d0> (a java.lang.Object)

? ? at org.ccgogoing.study.stacktrace.deadlock.TestThread1.run(TestThread1.java:20)

Found 1 deadlock.

從打印的堆棧中我們能看到"Found one Java-level deadlock:",即如果存在死鎖情況,堆棧中會直接給出死鎖的分析結果.

當一組Java線程發生死鎖的時候,那么意味著Game Over,這些線程永遠得被掛在那里了,永遠不可能繼續運行下去。當發生死鎖的線程在執行系統的關鍵功能時,那么這個死鎖可能會導致整個系統癱瘓,要想恢復系統,臨時也是唯一的規避方法是將系統重啟。然后趕快去修改導致這個死鎖的Bug。

注意:死鎖的兩個或多個線程是不消耗CPU的,有的人認為CPU100%的使用率是線程死鎖導致的,這個說法是完全錯誤的。死循環,并且在循環中代碼都是CPU密集型,才有可能導致CPU的100%使用率,像socket或者數據庫等IO操作是不怎么消耗CPU的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • 不知覺間工作已有一年了,閑下來的時候總會思考下,作為一名Java程序員,不能一直停留在開發業務使用框架上面。老話說...
    程序員七哥閱讀 3,302評論 0 5
  • 背景 10月2號凌晨12:08收到報警,所有請求失敗,處于完全不可用狀態 應用服務器共四臺resin,resin之...
    AGIHunt閱讀 22,062評論 6 16
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,711評論 18 399
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,127評論 0 62
  • 我想給媽媽一個擁抱 長大后很少陪伴你 常常在外各種忙碌 跑遍全國 卻無一張車票是回家的 要跟媽媽講聲“對不起” 我...
    Angel李子汐閱讀 422評論 17 24