JVM 命令指北(4)-jstack 命令

1. 功能簡介

jstack 用於打印出給定的 Java進程ID 或遠程調試服務的線程快照。線程快照是 Java 虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環造成的長時間等待。 通過 jstack 查看各個線程的調用堆棧,就可以知道停頓的線程到底在做什麼,或者等待什麼資源。 如果 Java 程序崩潰生成core文件jstack工具也可以獲取 core 文件的 Java方法棧和本地方法棧的信息,從而可以直達事故現場分析出程序是如何崩潰的。另外需注意機器上當前登錄用戶和 Java 進程啓動用戶不是同一個時,無法使用該命令

  • 需要注意的問題
    不同的 JAVA 虛機的線程 DUMP 的創建方法和文件格式是不一樣的,不同的 JVM 版本, dump信息也有差別。在實際運行中,往往一次 dump的信息,還不足以確認問題。建議產生三次 dump信息,如果每次 dump都指向同一個問題,才能確定問題的典型性

2. 使用方式

命令格式 參數說明
jstack [ option ] pid pid 爲 Java 應用程序的進程號,連接到正在運行的進程
jstack [ option ] executable core executable 爲產生core dump的 java可執行程序,core爲打印出的core文件,該命令可連接到核心文件
jstack [ option ] [ server-id@]remote-hostname-or-IP remote-hostname-or-ip 是遠程debug服務器的名稱或IP,server-id是唯一id,假如一臺主機上有多個遠程debug服務會需求區分
Options 功能
-F 當線程沒有響應的時候強制打印棧信息
-m 打印 Java 和 native c/c++ 框架的所有棧信息
-l 長列表. 打印關於鎖的附加信息,例如屬於 java.util.concurrent 的 ownable synchronizers 列表

常見的使用方式如下

// 打印指定 Java 進程的棧信息
jstack -l 44295
// 輸出指定 Java 進程的棧信息到 log 文件中
jstack -l 44295 > ~/44295.log

3. 分析注意

3.1 線程狀態

通過 jstack 命令來分析線程的情況,首先要知道線程都有哪些狀態,以下狀態是查看線程堆棧信息時可能會看到的:

RUNNABLE: 在虛擬機內執行的
BLOCKED: 受阻塞並等待監視器鎖,通常是多條線程爭奪 synchronized 鎖住資源失敗觸發
WATING:無限期等待另一個線程執行特定操作
TIMED_WATING:有時限的等待另一個線程的特定操作,通常由 sleep(n) 及定時任務觸發
TERMINATED:已退出的
在這裏插入圖片描述

3.2 線程信息的格式

參考 Java 對象鎖的 Monitor 實現,可以知道一個 Monitor 在某個時刻,只能被一個線程擁有,擁有鎖的線程就是 Active Thread,而其它線程都是 Waiting Thread,分別在兩個隊列 EntrySetWaitSet 裏面等待。在 EntrySet中等待的線程狀態是 Waiting for monitor entry,表示等待獲取鎖;而在 WaitSet中等待的線程狀態是 in Object.wait(),表示獲得鎖成功後又釋放,等待被喚醒

一個典型線程信息如下,可以看到在第二行中標出的是線程狀態 WAITING (on object monitor),表示在等待獲取 Monitor 對象,而第一行的in Object.wait() 則是等待狀態產生的原因,可以知道是線程當前執行條件不滿足,自己調用了 wait() 方法放棄鎖,等待被 notify()。而waiting on <no object reference available> 則是當前線程對 Monitor 鎖的操作狀態,表示使用synchronized申請對象鎖成功後,釋放鎖並在WaitSet 裏面等待被喚醒

“Disposer” #105 daemon prio=10 os_prio=31 cpu=35.51ms elapsed=241580.14s tid=0x00007fa74fb0f800 nid=0x20e87 in Object.wait() [0x000070000a46c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait([email protected]/Native Method)
- waiting on <no object reference available>
at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:155)
- waiting to re-lock in wait() <0x00000007493fc428> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove([email protected]/ReferenceQueue.java:176)
at com.sun.webkit.Disposer.run([email protected]/Disposer.java:122)
at java.lang.Thread.run([email protected]/Thread.java:834)
.
Locked ownable synchronizers:
- None

線程對 Monitor 對象的操作狀態 意義
locked <地址> 目標 使用synchronized申請對象鎖成功,監視器的擁有者
waiting to lock <地址> 目標 使用synchronized申請對象鎖未成功,在EntrySet 隊列等待
waiting on <地址> 目標 使用synchronized申請對象鎖成功後,釋放鎖在WaitSet 區等待
parking to wait for <地址> 目標 等待獲取鎖時調用 park() 掛起線程
線程狀態描述 線程狀態
runnable 狀態一般爲RUNNABLE
in Object.wait() WaitSet等待區等待,狀態爲 WAITING 或 TIMED_WAITING
waiting for monitor entry EntrySet 進入區等待,狀態爲 BLOCKED
waiting on condition WaitSet等待區等待,線程被 park
sleeping 休眠的線程,調用了Thread.sleep()

3.3 重點信息

搜索關鍵字 含義
Deadlock 死鎖,重點關注
Waiting on condition 等待資源,重點關注
Waiting on monitor entry 等待獲取監視器,重點關注
Blocked 阻塞,重點關注
  1. Deadlock關鍵字
    這個一般指多個線程調用時,自己佔用了一部分資源,又需要請求對方佔用的資源導致無限期等待對方釋放資源的情況。這種情況堆棧寫的會很明顯,它會打印 Found one Java-level deadlock,然後指出造成死鎖的線程的信息

  2. Blocked 關鍵字
    線程狀態是 Blocked,說明線程等待資源超時。此時可查看其 waiting to lock <0x0000000xxxxxxx>信息,在 dump 日誌裏查找這個地址字符串,如發現有大量線程都在等待給這個地址上鎖,可能在代碼級別已經存在衝突的調用。此時如果能在日誌裏找到誰獲得了這個鎖(關鍵字locked <0x0000000xxxxxxx>),則可以有效找到事故現場

  3. Wait on condition 關鍵字
    該狀態表示等待資源,或等待某個條件的發生,具體原因需結合堆棧信息來分析。最常見的情況就是線程處於sleep狀態,等待被喚醒

    • 如果線程是用戶線程,則證明該線程正在等待資源。一般是大量讀取某資源,且該資源採用了資源鎖的情況下,線程進入等待狀態,等待資源的讀取
    • 如果發現有大量的線程都在處在 Wait on condition,從線程 stack 看正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆,因爲網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;另一種情況也可能是網絡空閒,但由於路由等問題,導致包無法正常到達
  4. 持續運行的IO
    IO 操作可以在 RUNNABLE狀態達成阻塞,例如數據庫死鎖、網絡讀寫。 需要格外注意對 IO 線程真實狀態的分析,比如readLine()造成的 IO 阻塞,一般被捕捉到RUNNABLEIO 調用都是有問題的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章