Java 程序員必備:jstack 命令解析
前言
如果有一天,你的 Java 程序長時間停頓,也許是它病了,需要用 jstack 拍個片子分析分析,才能診斷具體什麼病症,是死鎖綜合徵,還是死循環等其他病症,本文我們一起來學習 jstack 命令~
jstack 的功能
jstack 是 JVM 自帶的 Java 堆棧跟蹤工具,它用於打印出給定的 java 進程 ID、core file、遠程調試服務的 Java 堆棧信息.
jstack prints Java stack traces of Java threads for a given Java process or
core file or a remote debug server.
jstack 命令用於生成虛擬機當前時刻的線程快照。
線程快照是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因, 如線程間死鎖、死循環、請求外部資源導致的長時間等待等問題。
線程出現停頓的時候通過 jstack 來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做什麼事情,或者等待什麼資源。
如果 java 程序崩潰生成 core 文件,jstack 工具可以用來獲得 core 文件的 java stack 和 native stack 的信息,從而可以輕鬆地知道 java 程序是如何崩潰和在程序何處發生問題。
另外,jstack 工具還可以附屬到正在運行的 java 程序中,看到當時運行的 java 程序的 java stack 和 native stack 的信息, 如果現在運行的 java 程序呈現 hung 的狀態,jstack 是非常有用的。
jstack 用法
jstack 命令格式如下
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
-
executable Java executable from which the core dump was produced.(可能是產生 core dump 的 java 可執行程序)
-
core 將被打印信息的 core dump 文件
-
remote-hostname-or-IP 遠程 debug 服務的主機名或 ip
-
server-id 唯一 id, 假如一臺主機上多個遠程 debug 服務
最常用的是
jstack [option] <pid> // 打印某個進程的堆棧信息
option 參數說明如下:
線程狀態等基礎回顧
線程狀態簡介
jstack 用於生成線程快照的,我們分析線程的情況,需要複習一下線程狀態吧,拿小凳子坐好,複習一下啦~
Java 語言定義了 6 種線程池狀態:
-
New:創建後尚未啓動的線程處於這種狀態,不會出現在 Dump 中。
-
RUNNABLE:包括 Running 和 Ready。線程開啓 start()方法,會進入該狀態,在虛擬機內執行的。
-
Waiting:無限的等待另一個線程的特定操作。
-
Timed Waiting:有時限的等待另一個線程的特定操作。
-
阻塞(Blocked):在程序等待進入同步區域的時候,線程將進入這種狀態,在等待監視器鎖。
-
結束(Terminated):已終止線程的線程狀態,線程已經結束執行。
Dump 文件的線程狀態一般其實就以下 3 種:
-
RUNNABLE,線程處於執行中
-
BLOCKED,線程被阻塞
-
WAITING,線程正在等待
Monitor 監視鎖
因爲 Java 程序一般都是多線程運行的,Java 多線程跟監視鎖環環相扣,所以我們分析線程狀態時,也需要回顧一下 Monitor 監視鎖知識。
Monitor 的工作原理圖如下:
-
線程想要獲取 monitor, 首先會進入 Entry Set 隊列,它是 Waiting Thread,線程狀態是 Waiting for monitor entry。
-
當某個線程成功獲取對象的 monitor 後, 進入 Owner 區域,它就是 Active Thread。
-
如果線程調用了 wait() 方法,則會進入 Wait Set 隊列,它會釋放 monitor 鎖,它也是 Waiting Thread,線程狀態 in Object.wait()
-
如果其他線程調用 notify() / notifyAll() ,會喚醒 Wait Set 中的某個線程,該線程再次嘗試獲取 monitor 鎖,成功即進入 Owner 區域。
Dump 文件分析關注重點
-
runnable,線程處於執行中
-
deadlock,死鎖(重點關注)
-
blocked,線程被阻塞 (重點關注)
-
Parked,停止
-
locked,對象加鎖
-
waiting,線程正在等待
-
waiting to lock 等待上鎖
-
Object.wait(),對象等待中
-
waiting for monitor entry 等待獲取監視器(重點關注)
-
Waiting on condition,等待資源(重點關注),最常見的情況是線程在等待網絡的讀寫
實戰案例 1:jstack 分析死鎖問題
-
什麼是死鎖?
-
如何用 jstack 排查死鎖?
什麼是死鎖?
死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法進行下去。
如何用如何用 jstack 排查死鎖問題
先來看一段會產生死鎖的 Java 程序,源碼如下:
/**
* Java 死鎖demo
*/
public class DeathLockTest {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
System.out.println(Thread.currentThread().getName() + " get the lock1");
Thread.sleep(1000);
lock2.lock();
System.out.println(Thread.currentThread().getName() + " get the lock2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
System.out.println(Thread.currentThread().getName() + " get the lock2");
Thread.sleep(1000);
lock1.lock();
System.out.println(Thread.currentThread().getName() + " get the lock1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//設置線程名字,方便分析堆棧信息
t1.setName("mythread-jay");
t2.setName("mythread-tianluo");
t1.start();
t2.start();
}
public static void main(String[] args) {
deathLock();
}
}
運行結果:
顯然,線程 jay 和線程 tianluo 都是隻執行到一半,就陷入了阻塞等待狀態~
jstack 排查 Java 死鎖步驟
-
在終端中輸入 jsp 查看當前運行的 java 程序
-
使用 jstack -l pid 查看線程堆棧信息
-
分析堆棧信息
在終端中輸入 jsp 查看當前運行的 java 程序
通過使用 jps 命令獲取需要監控的進程的 pid,我們找到了23780 DeathLockTest
使用 jstack -l pid 查看線程堆棧信息
由上圖,可以清晰看到死鎖信息:
-
mythread-tianluo 等待這個鎖 “0x00000000d61ae3a0”,這個鎖是由於 mythread-jay 線程持有。
-
mythread-jay 線程等待這個鎖 “0x00000000d61ae3d0”, 這個鎖是由 mythread-tianluo 線程持有。
還原死鎖真相
- mythread-tianluo 的線程處於等待(waiting)狀態,持有 “0x00000000d61ae3d0” 鎖,等待 “0x00000000d61ae3a0” 的鎖
“mythread-jay" 線程堆棧信息分析如下:
- mythread-tianluo 的線程處於等待(waiting)狀態,持有 “0x00000000d61ae3a0” 鎖,等待 “0x00000000d61ae3d0” 的鎖
實戰案例 2:jstack 分析 CPU 過高問題
來個導致 CPU 過高的 demo 程序,一個死循環,哈哈~
/**
* 有個導致CPU過高程序的demo,死循環
*/
public class JstackCase {
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
Task task1 = new Task();
Task task2 = new Task();
executorService.execute(task1);
executorService.execute(task2);
}
public static Object lock = new Object();
static class Task implements Runnable{
public void run() {
synchronized (lock){
long sum = 0L;
while (true){
sum += 1;
}
}
}
}
}
jstack 分析 CPU 過高步驟
-
top
-
top -Hp pid
-
jstack pid
-
jstack -l [PID] >/tmp/log.txt
-
分析堆棧信息
1.top
在服務器上,我們可以通過 top 命令查看各個進程的 cpu 使用情況,它默認是按 cpu 使用率由高到低排序的
由上圖中,我們可以找出 pid 爲 21340 的 java 進程,它佔用了最高的 cpu 資源,兇手就是它,哈哈!
2. top -Hp pid
通過 top -Hp 21340 可以查看該進程下,各個線程的 cpu 使用情況,如下:
可以發現 pid 爲 21350 的線程,CPU 資源佔用最高~,嘻嘻,小本本把它記下來,接下來拿 jstack 給它拍片子~
3. jstack pid
通過 top 命令定位到 cpu 佔用率較高的線程之後,接着使用 jstack pid 命令來查看當前 java 進程的堆棧狀態,jstack 21350
後,內容如下:
4. jstack -l [PID] >/tmp/log.txt
其實,前 3 個步驟,堆棧信息已經出來啦。但是一般在生成環境,我們可以把這些堆棧信息打到一個文件裏,再回頭仔細分析哦~
5. 分析堆棧信息
我們把佔用 cpu 資源較高的線程 pid(本例子是 21350),將該 pid 轉成 16 進制的值
在 thread dump 中,每個線程都有一個 nid,我們找到對應的 nid(5366),發現一直在跑(24 行)
這個時候,可以去檢查代碼是否有問題啦~ 當然,也建議隔段時間再執行一次 stack 命令,再一份獲取 thread dump,畢竟兩次拍片結果(jstack)對比,更準確嘛~
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/OGqWzj-QcFvoUgiDQ-MVsw