JVM 執行緒堆疊分析過程詳解

oschina發表於2015-03-20

在這篇文章裡我將教會你如何分析JVM的執行緒堆疊以及如何從堆疊資訊中找出問題的根因。在我看來執行緒堆疊分析技術是Java EE產品支援工程師所必須掌握的一門技術。線上程堆疊中儲存的資訊,通常遠超出你的想象,我們可以在工作中善加利用這些資訊。

我的目標是分享我過去十幾年來線上程分析中積累的知識和經驗。這些知識和經驗是在各種版本的JVM以及各廠商的JVM供應商的深入分析中獲得的,在這個過程中我也總結出大量的通用問題模板。

那麼,準備好了麼,現在就把這篇文章加入書籤,在後續幾週中我會給大家帶來這一系列的專題文章。還等什麼,請趕緊給你的同事和朋友分享這個執行緒分析培訓計劃吧。

聽上去是不錯,我確實是應該提升我的執行緒堆疊分析技能…但我要從哪裡開始呢?

我的建議是跟隨我來完成這個執行緒分析培訓計劃。下面是我們會覆蓋到的培訓內容。同時,我會把我處理過的實際案例分享給大家,以便與大家學習和理解。

1) 執行緒堆疊概述及基礎知識

2) 執行緒堆疊的生成原理以及相關工具

3) 不同JVM執行緒堆疊的格式的差異(Sun HotSpot、IBM JRE、Oracal JRockit)

4) 執行緒堆疊日誌介紹以及解析方法

5) 執行緒堆疊的分析和相關的技術

6) 常見的問題模板(執行緒竟態、死鎖、IO呼叫掛死、垃圾回收/OutOfMemoryError問題、死迴圈等)

7) 執行緒堆疊問題例項分析

我希望這一系列的培訓能給你帶來確實的幫助,所以請持續關注每週的文章更新。

但是如果我在學習過程中有疑問或者無法理解文章中的內容該怎麼辦?

不用擔心,把我當做你的導師就好。任何關於執行緒堆疊的問題都可以諮詢我(前提是問題不能太low)。請隨意選擇下面的幾種方式與我取得聯絡:

1) 直接本文下面發表評論(不好意思的話可以匿名)

2) 將你的執行緒堆疊資料提交到Root Cause Analysis forum

3) 發Email給我,地址是 @phcharbonneau@hotmail.com

能幫我分析我們產品上遇到的問題麼?

當然可以,如果你願意的話可以把你的堆疊現場資料通過郵件或論壇 Root Cause Analysis forum發給我。處理實際問題是才是學習提升技能的王道。

我真心期望大家能夠喜歡這個培訓。所以我會盡我所能去為你提供高質量的材料,並回答大家的各種問題。

在介紹執行緒堆疊分析技術和問題模式之前,先要給大家講講基礎的內容。所以在這篇帖子裡,我將先覆蓋到最基本的內容,這樣大家就能更好的去理解JVM、中介軟體、以及Java EE容器之間的互動。

Java VM 概述

Java虛擬機器是Jave EE 平臺的基礎。它是中介軟體和應用程式被部署和執行的地方。

JVM向中介軟體軟體和你的Java/Java EE程式提供了下面這些東西:

–   (二進位制形式的)Java / Java EE 程式執行環境

–   一些程式功能特性和工具 (IO 基礎設施,資料結構,執行緒管理,安全,監控 等等.)

–   藉助垃圾回收的動態記憶體分配與管理

你的JVM可以駐留在許多的作業系統 (Solaris, AIX, Windows 等等.)之上,並且能根據你的物理伺服器配置,你可以在每臺物理/虛擬伺服器上安裝1到多個JVM程式.

JVM與中介軟體之間的互動

下面這張圖展示了JVM、中介軟體和應用程式之間的高層互動模型。

圖中展示的JVM、中介軟體和應用程式件之間的一些簡單和典型的互動。如你所見,標準Java EE應用程式的執行緒的分配實在中介軟體核心與JVM之間完成的。(當然也有例外,應用程式可以直接呼叫API來建立執行緒,這種做法並不常見,而且在使用的過程中也要特別的小心)

同時,請注意一些執行緒是由JVM內部來進行管理的,典型的例子就是垃圾回收執行緒,JVM內部使用這個執行緒來做並行的垃圾回收處理。

因為大多數的執行緒分配都是由Java EE容器完成的,所以能夠理解和認識執行緒堆疊跟蹤,並能從執行緒堆疊資料中識別出它來,對你而言很重要. 這可以讓你能夠快速的知道Java EE容器正要執行的是什麼型別的請求.

從一個執行緒轉儲堆疊的分析角度來看,你將能瞭解從JVM發現的執行緒池之間的不同,並識別出請求的型別.

最後一節會向你提供對於HotSop VM而言什麼是JVM執行緒堆疊的一個概述,還有你將會遇到的各種不同的執行緒. 而對 IBM VM 執行緒堆疊形式詳細內容將會在第四節向你提供.

請注意你可以從根本原因分析論壇獲得針對本文的執行緒堆疊示例.

JVM 執行緒堆疊——它是什麼?

JVM執行緒堆疊是一個給定時間的快照,它能向你提供所有被建立出來的Java執行緒的完整清單.

每一個被發現的Java執行緒都會給你如下資訊:

– 執行緒的名稱;經常被中介軟體廠商用來識別執行緒的標識,一般還會帶上被分配的執行緒池名稱以及狀態 (執行,阻塞等等.)

– 執行緒型別 & 優先順序,例如 : daemon prio=3 ** 中介軟體程式一般以後臺守護的形式建立他們的執行緒,這意味著這些執行緒是在後臺執行的;它們會向它們的使用者提供服務,例如:向你的Java EE應用程式 **

– Java執行緒ID,例如 : tid=0x000000011e52a800 ** 這是通過 java.lang.Thread.getId() 獲得的Java執行緒ID,它常常用自增長的長整形 1..n** 實現

– 原生執行緒ID,例如 : nid=0x251c** ,之所以關鍵是因為原生執行緒ID可以讓你獲得諸如從作業系統的角度來看那個執行緒在你的JVM中使用了大部分的CPU時間等這樣的相關資訊. **

– Java執行緒狀態和詳細資訊,例如: waiting for monitor entry [0xfffffffea5afb000] java.lang.Thread.State: BLOCKED (on object monitor)
** 可以快速的瞭解到執行緒狀態極其當前阻塞的可能原因 **

– Java執行緒棧跟蹤;這是目前為止你能從執行緒堆疊中找到的最重要的資料. 這也是你花費最多分析時間的地方,因為Java棧跟蹤向提供了你將會在稍後的練習環節瞭解到的導致諸多型別的問題的根本原因,所需要的90%的資訊。

– Java 堆記憶體分解; 從HotSpot VM 1.6版本開始,線上程堆疊的末尾處可以看到HotSpot的記憶體使用情況,比如說Java的堆記憶體(YoungGen, OldGen) & PermGen 空間。這個資訊對分析由於頻繁GC而引起的問題時,是很有用的。你可以使用已知的執行緒資料或模式做一個快速的定位。

Heap  
PSYoungGen      total 466944K, used 178734K [0xffffffff45c00000, 0xffffffff70800000, 0xffffffff70800000)  
eden space 233472K, 76% used [0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)  
from space 233472K, 0% used [0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)  
to   space 233472K, 0% used [0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)  
PSOldGen        total 1400832K, used 1400831K [0xfffffffef0400000, 0xffffffff45c00000, 0xffffffff45c00000)  
object space 1400832K, 99% used [0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)  
PSPermGen       total 262144K, used 248475K [0xfffffffed0400000, 0xfffffffee0400000, 0xfffffffef0400000)  
object space 262144K, 94% used [0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)

執行緒堆疊資訊大拆解

為了讓大家更好的理解,給大家提供了下面的這張圖,在這張圖中將HotSpot VM上的執行緒堆疊資訊和執行緒池做了詳細的拆解,如下圖所示:

上圖中可以看出執行緒堆疊是由多個不同部分組成的。這些資訊對問題分析都很重要,但對不同的問題模式的分析會使用不同的部分(問題模式會在後面的文章中做模擬和演示。)

現在通過這個分析樣例,給大家詳細解釋一下HoteSpot上執行緒堆疊資訊中的各個組成部分:

# Full thread dump標示符

“Full thread dump”是一個全域性唯一的關鍵字,你可以在中介軟體和單機版本Java的執行緒堆疊資訊的輸出日誌中找到它(比如說在UNIX下使用:kill -3 <PID> )。這是執行緒堆疊快照的開始部分。

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

# Java EE 中介軟體,第三方以及自定義應用軟體中的執行緒

這個部分是整個執行緒堆疊的核心部分,也是通常需要花費最多分析時間的部分。堆疊中執行緒的個數取決你使用的中介軟體,第三方庫(可能會有獨立執行緒)以及你的應用程式(如果建立自定義執行緒,這通常不是一個很好的實踐)。

在我們的示例執行緒堆疊中,WebLogic是我們所使用的中介軟體。從Weblogic 9.2開始, 會使用一個用“’weblogic.kernel.Default (self-tuning)”唯一標識的能自行管理的執行緒池

"[STANDBY] ExecuteThread: '414' for queue: 'weblogic.kernel.Default (self-tuning)'" daemon prio=3 tid=0x000000010916a800 nid=0x2613 in Object.wait() [0xfffffffe9edff000]  
   java.lang.Thread.State: WAITING (on object monitor)  
        at java.lang.Object.wait(Native Method)  
        - waiting on <0xffffffff27d44de0> (a weblogic.work.ExecuteThread)  
        at java.lang.Object.wait(Object.java:485)  
        at weblogic.work.ExecuteThread.waitForRequest(ExecuteThread.java:160)  
        - locked <0xffffffff27d44de0> (a weblogic.work.ExecuteThread)  
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)

# HotSpot VM 執行緒

這是一個有Hotspot VM管理的內部執行緒,用於執行內部的原生操作。一般你不用對此操太多心,除非你(通過相關的執行緒堆疊以及 prstat或者原生執行緒Id)發現很高的CPU佔用率.

"VM Periodic Task Thread" prio=3 tid=0x0000000101238800 nid=0x19 waiting on condition

# HotSpot GC 執行緒

當使用 HotSpot 進行並行 GC (如今在使用多個物理核心的環境下很常見), 預設建立的HotSpot VM 或者每個JVM管理一個有特定標識的GC執行緒時. 這些GC執行緒可以讓VM以並行的方式執行其週期性的GC清理, 這會導致GC時間的總體減少;與此同時的代價是CPU的使用時間會增加.

"GC task thread#0 (ParallelGC)" prio=3 tid=0x0000000100120000 nid=0x3 runnable  
"GC task thread#1 (ParallelGC)" prio=3 tid=0x0000000100131000 nid=0x4 runnable  
………………………………………………………………………………………………………………………………………………………………

這事非常關鍵的資料,因為當你遇到跟GC有關的問題,諸如過度GC、記憶體洩露等問題是,你將可以利用這些執行緒的原生Id值關聯的作業系統或者Java執行緒,進而發現任何對CPI時間的高佔用. 未來的文章你將會了解到如何識別並診斷這樣的問題.

# JNI 全域性引用計數

JNI (Java 本地介面)的全域性引用就是從原生程式碼到由Java垃圾收集器管理的Java物件的基本的物件引用. 它的角色就是阻止對仍然在被原生程式碼使用,但是技術上已經不是Java程式碼中的“活動的”引用了的物件的垃圾收集.

同時為了偵測JNI相關的洩露而留意JNI引用也很重要. 如果你的程式直接使用了JNI,或者像監聽器這樣的第三方工具,就容易造成本地的記憶體洩露.

JNI global references: 1925

# Java 堆疊使用檢視

這些資料被新增回了 JDK 1 .6 ,向你提供有關Hotspot堆疊的一個簡短而快速的檢視. 我發現它在當我處理帶有過高CPU佔用的GC相關的問題時非常有用,你可以在一個單獨的快照中同時看到執行緒堆疊以及Java堆的資訊,讓你當時就可以在一個特定的Java堆記憶體空間中解析(或者排除)出任何的關鍵點. 你如在我們的示例執行緒堆疊中所見,Java 的堆 OldGen 超出了最大值!

Heap  
 PSYoungGen      total 466944K, used 178734K [0xffffffff45c00000, 0xffffffff70800000, 0xffffffff70800000)  
  eden space 233472K, 76% used [0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)  
  from space 233472K, 0% used [0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)  
  to   space 233472K, 0% used [0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)  
 PSOldGen        total 1400832K, used 1400831K [0xfffffffef0400000, 0xffffffff45c00000, 0xffffffff45c00000)  
  object space 1400832K, 99% used [0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)  
 PSPermGen       total 262144K, used 248475K [0xfffffffed0400000, 0xfffffffee0400000, 0xfffffffef0400000)  
  object space 262144K, 94% used [0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)

我希望這篇文章能對你理解Hotspot VM執行緒堆疊的基本資訊有所幫助。下一篇文章將會向你提供有關IBM VM的執行緒堆疊概述和分析.

相關文章