JVM:如何分析線程堆棧

英文原文:JVM: How to analyze Thread Dumphtml

參與翻譯 (2人) : leoxu, YiHunter java

在這篇文章裏我將教會你如何分析JVM的線程堆棧以及如何從堆棧信息中找出問題的根因。在我看來線程堆棧分析技術是Java EE產品支持工程師所必須掌握的一門技術。在線程堆棧中存儲的信息,一般遠超出你的想象,咱們能夠在工做中善加利用這些信息。 web

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

那麼,準備好了麼,如今就把這篇文章加入書籤,在後續幾週中我會給你們帶來這一系列的專題文章。還等什麼,請趕忙給你的同事和朋友分享這個線程分析培訓計劃吧bash

 


 

聽上去是不錯,我確實是應該提高個人線程堆棧分析技能...但我要從哪裏開始呢?服務器

個人建議是跟隨我來完成這個線程分析培訓計劃。下面是咱們會覆蓋到的培訓內容。同時,我會把我處理過的實際案例分享給你們,以便與你們學習和理解。數據結構

1) 線程堆棧概述及基礎知識
2) 線程堆棧的生成原理以及相關工具
3) 不一樣JVM線程堆棧的格式的差別(Sun HotSpot、IBM JRE、Oracal JRockit)
4) 線程堆棧日誌介紹以及解析方法
5) 線程堆棧的分析和相關的技術
6) 常見的問題模板(線程竟態、死鎖、IO調用掛死、垃圾回收/OutOfMemoryError問題、死循環等)
7) 線程堆棧問題實例分析java-ee


可是若是我在學習過程當中有疑問或者沒法理解文章中的內容該怎麼辦? jvm

不用擔憂,把我當作你的導師就好。任何關於線程堆棧的問題均可以諮詢我(前提是問題不能太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的線程堆棧概述和分析.

請自由的向本文發表觀點或疑問.

引用: 如何分析線程堆棧 —— 第一部分 如何分析線程堆棧——第二部分 & 如何分析線程堆棧——第三部分 來自咱們的 Java EE Support Patterns & Java Tutorial 博客上的 JCG 合做者Hugues Charbonneau.

相關文章
相關標籤/搜索