jstack(Stack Trace For Java, 官方鏈接)用于生成java虛擬機(jī)某個(gè)進(jìn)程在當(dāng)前時(shí)刻的線程快照(一般稱為threaddump或javacore文件,由線程的調(diào)用堆棧組成),用來(lái)定位線程長(zhǎng)時(shí)間停頓的原因,如死循環(huán)、死鎖等。
一般在用該工具時(shí)主要分為三步:
-
獲取進(jìn)程id
方法1:
jps -l方法2:
ps -ef|grep java方法3:
lsof -i:<port>上述三個(gè)方法根據(jù)具體情況選擇使用,方法1最簡(jiǎn)便(適合java進(jìn)程少的情況),方法2信息更詳盡,方法3則適合有多個(gè)java進(jìn)程,根據(jù)方法1可能分辨不出來(lái)想找的進(jìn)程,而需要通過(guò)端口號(hào)進(jìn)行定位。
-
獲取進(jìn)程中的線程狀態(tài)
top -Hp <pid>注意該命令只能在linux中使用,而在macOS上不能使用。因?yàn)閘inux將線程作為輕量級(jí)進(jìn)程也分配了pid,而macOS并沒(méi)有這么處理。
如果通過(guò)上述命令我們發(fā)現(xiàn)進(jìn)程中的某個(gè)線程指標(biāo)不正常,想重點(diǎn)關(guān)注,這時(shí)需要將線程的pid通過(guò)下面命令轉(zhuǎn)化為十六進(jìn)進(jìn)制,方便在線程快照信息中查找。
? echo 'obase=16; ibase=10; <pid>' | bc | tr '[A-Z]' '[a-z]'
-
查看并分析線程快照信息
用
jstack <pid>命令可以在控制臺(tái)打印線程快照信息,jstack <pid> > <path>可以將線程快照信息輸出到文件。如下為打印出來(lái)的一條線程快照,由10個(gè)部分組成。image-20181111102804259- 線程名稱,如果程序中沒(méi)有顯示給線程命名則顯示默認(rèn)名稱
- 線程序號(hào),相當(dāng)于程序所有線程的一個(gè)編號(hào)
- 線程優(yōu)先級(jí),java中線程的默認(rèn)優(yōu)先級(jí)為5
- 線程系統(tǒng)優(yōu)先級(jí)
- 線程id
- 線程native id,在linux中對(duì)應(yīng)線程的輕量級(jí)進(jìn)程id,十六進(jìn)制,通過(guò)該字段都與top命令中的線程對(duì)應(yīng)起來(lái)。
- 線程描述
- 線程棧的起始地址
- 線程狀態(tài)
- 線程執(zhí)行堆棧,具體到代碼的行數(shù)
需要注意的是執(zhí)行該命令時(shí)當(dāng)前用戶必須為啟動(dòng)該進(jìn)程的用戶,否則會(huì)失敗,即使是root用戶也不行。
接下來(lái)將通過(guò)兩個(gè)實(shí)例來(lái)對(duì)該工具的使用進(jìn)行詳細(xì)說(shuō)明:
實(shí)例1: 利用線程快照分析CPU占用過(guò)高的原因
用于演示CPU示例代碼如下: 啟動(dòng)三個(gè)線程,且某個(gè)線程獲取唯一的鎖后一直沒(méi)有釋放,可想而知,另外兩個(gè)線程將處于阻塞狀態(tài)。
import java.util.UUID;
public class HighCPUCase {
public static Object lock = new Object();
public static void main(String[] args) {
new Thread(new Task(), "task1").start();
new Thread(new Task(), "task2").start();
new Thread(new Task(), "task3").start();
}
static class Task implements Runnable{
@Override
public void run(){
synchronized (lock){
while (true){
System.out.println(UUID.randomUUID().toString());
}
}
}
}
}
現(xiàn)在用文章開(kāi)始的三步曲來(lái)驗(yàn)證我們的猜想。
-
首先獲取進(jìn)程id
image-20181110225814867 -
獲取進(jìn)程中消耗CPU最高的線程
image-20181110230019853將23068用如下命令轉(zhuǎn)化為16進(jìn)制后為5a1c
image-20181110230321690 -
查看并分析線程快照
image-20181110231223530? 上圖中藍(lán)色線框?yàn)槲覀冃枰攸c(diǎn)關(guān)注的內(nèi)容,可以看到5a1c(由第2步得到)對(duì)應(yīng)的線程為task1,該線程處于RUNNABLE狀態(tài),且下在執(zhí)行HighCPUCase.java中的第17行,而線程task2和task3都因?yàn)榈却i而處于BLOCKED狀態(tài)。仔細(xì)觀察可以發(fā)現(xiàn)task2和task3等待的鎖與task1獲得的鎖完全相同。
? 由此我們下結(jié)論,CPU高的原因是因?yàn)榫€程task1,而它正在執(zhí)行HighCPUCase.java中的第17行,線程task2和task3處于阻塞狀態(tài)是因?yàn)閠ask1獲得唯一一把鎖后一直沒(méi)有釋放。然后我們根據(jù)結(jié)論再去看代碼,分析得完全沒(méi)毛病。
實(shí)例2: 利用線程快照分析死鎖
下面為演示死鎖的代碼,模擬有兩個(gè)線程A,B, 兩把鎖1,2,當(dāng)線程1成功拿到鎖1嘗試去拿鎖2發(fā)現(xiàn)拿不到,而此時(shí)線程2成功拿到鎖2嘗試去拿鎖1也拿不到,誰(shuí)也不讓誰(shuí)就只能干耗著了,導(dǎo)致程序一直不能結(jié)束。
public class DeadLockCase {
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
new Thread(new SyncThread(o1, o2), "t1").start();
new Thread(new SyncThread(o2, o1), "t2").start();
}
static class SyncThread implements Runnable {
private Object lock1;
private Object lock2;
public SyncThread(Object o1, Object o2){
this.lock1 = o1;
this.lock2 = o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on " + lock1);
synchronized (lock1) {
System.out.println(name + " acquired lock on " + lock1);
work();
System.out.println(name + " acquiring lock on " + lock2);
synchronized (lock2) {
System.out.println(name + " acquired lock on " + lock2);
work();
}
System.out.println(name + " released lock on " + lock2);
}
System.out.println(name + " released lock on " + lock1);
}
private void work() {
try {
//模擬死鎖的關(guān)鍵,保證線程1只能獲取一個(gè)鎖,而線程2能獲取到另一個(gè)鎖
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
運(yùn)行上面代碼輸出如下,可以看到符合我們的預(yù)期:

下面還是通過(guò)線程快照來(lái)反推:
-
首先拿到進(jìn)程id
image-20181110235201192 -
查看進(jìn)程內(nèi)的線程情況
image-20181110235336030可以看到所有線程無(wú)異常指標(biāo),這一步還不知道哪一個(gè)線程有問(wèn)題。
23152對(duì)就的十六進(jìn)制數(shù)為5a70, 23144對(duì)應(yīng)的十六進(jìn)制數(shù)為5a68,接下來(lái)就重點(diǎn)關(guān)注這兩個(gè)。
-
查看線程快照
開(kāi)始部分:細(xì)心觀察發(fā)現(xiàn)兩個(gè)線程處于BLOCKED狀態(tài)是因?yàn)樵诘认嗷ブg的鎖。
image-20181111000447280結(jié)尾部分:很明確的告訴了你發(fā)現(xiàn)了一個(gè)java級(jí)別死鎖,t2在待t1手上的鎖,t1在等t2手上的鎖。
在實(shí)際生產(chǎn)中肯定比這本文中涉及到的兩個(gè)例子復(fù)雜,但如果能先把基礎(chǔ)的學(xué)會(huì),碰到問(wèn)題也不會(huì)慌而是學(xué)會(huì)去分解復(fù)雜的問(wèn)題,大而化小,最后方能解決問(wèn)題。
參考文章:







