前言
如果有一天,你的Java程序長時間停頓,也許是它病了,需要用jstack拍個片子分析分析,才能診斷具體什麼病症,是死鎖綜合徵,還是死循環等其他病症,本文我們一起來學習jstack命令~
jstack 的功能
jstack用法
線程狀態等基礎回顧
實戰案例1:jstack 分析死鎖
實戰案例2:jstack 分析CPU 過高
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參數說明如下:
選項 | 作用 |
---|---|
-F | 當正常輸出的請求不被響應時,強制輸出線程堆棧 |
-m | 如果調用到本地方法的話,可以顯示C/C++的堆棧 |
-l | 除堆棧外,顯示關於鎖的附加信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況 |
線程狀態等基礎回顧
線程狀態簡介
jstack用於生成線程快照的,我們分析線程的情況,需要複習一下線程狀態吧,拿小凳子坐好,複習一下啦~
Java語言定義了6種線程池狀態:
New:創建後尚未啓動的線程處於這種狀態,不會出現在Dump中。
RUNNABLE:包括Running和Ready。線程開啓start()方法,會進入該狀態,在虛擬機內執行的。
Waiting:無限的等待另一個線程的特定操作。
Timed Waiting:有時限的等待另一個線程的特定操作。
阻塞(Blocked):在程序等待進入同步區域的時候,線程將進入這種狀態,在等待監視器鎖。
結束(Terminated):已終止線程的線程狀態,線程已經結束執行。
Dump文件的線程狀態一般其實就以下3種:
RUNNABLE,線程處於執行中
BLOCKED,線程被阻塞
WAITING,線程正在等待
Monitor 監視鎖
因爲Java程序一般都是多線程運行的,Java多線程跟監視鎖環環相扣,所以我們分析線程狀態時,也需要回顧一下Monitor監視鎖知識。
有關於線程同步關鍵字Synchronized與監視鎖的愛恨情仇,有興趣的夥伴可以看一下我這篇文章Synchronized解析——如果你願意一層一層剝開我的心
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,我們找到了 23780DeathLockTest
使用 jstack -l pid 查看線程堆棧信息
由上圖,可以清晰看到死鎖信息:
mythread-tianluo 等待這個鎖 “0x00000000d61ae3a0”,這個鎖是由mythread-jay線程持有。
mythread-jay線程等待這個鎖“0x00000000d61ae3d0”,這個鎖是由mythread-tianluo 線程持有。
還原死鎖真相
“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
2.top -Hp pid
3. jstack pid
4. jstack -l [PID] >/tmp/log.txt
5. 分析堆棧信息
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進程的堆棧狀態, jstack21350
後,內容如下:
4. jstack -l [PID] >/tmp/log.txt
其實,前3個步驟,堆棧信息已經出來啦。但是一般在生成環境,我們可以把這些堆棧信息打到一個文件裏,再回頭仔細分析哦~
5. 分析堆棧信息
我們把佔用cpu資源較高的線程pid(本例子是21350),將該pid轉成16進制的值
在thread dump中,每個線程都有一個nid,我們找到對應的nid(5366),發現一直在跑(24行)
這個時候,可以去檢查代碼是否有問題啦~ 當然,也建議隔段時間再執行一次stack命令,再一份獲取thread dump,畢竟兩次拍片結果(jstack)對比,更準確嘛~
參考與感謝
jvm 性能調優工具之 jstack (https://www.jianshu.com/p/025cb069cb69)
如何使用jstack分析線程狀態 (https://www.jianshu.com/p/6690f7e92f27)
Java命令學習系列(二)—Jstack(http://www.hollischuang.com/archives/110)
個人公衆號
覺得寫得好的小夥伴給個點贊+關注啦,謝謝~
如果有寫得不正確的地方,麻煩指出,感激不盡。
同時非常期待小夥伴們能夠關注我公衆號,後面慢慢推出更好的乾貨~嘻嘻
github地址:https://github.com/whx123/JavaHome