概述
jstack是jdk自帶的線程堆棧分析工具,使用該命令可以查看或導出 Java 應用程序中線程堆棧信息。
Jstack 用法
Usage:
jstack [-l] <pid> (連接到正在運行的進程)
jstack -F [-m] [-l] <pid> (連接到掛起的進程)
jstack [-m] [-l] <executable> <core> (連接到核心文件)
jstack [-m] [-l] [server_id@]<remote server IP or hostname> (連接到遠程調試服務器)Options:
-F 強制線程轉儲。當jstack <pid>不響應時使用(進程掛起)
-m 同時打印java和本機幀(混合模式)
-l 長清單。打印關於鎖的附加信息
-h or -help 打印此幫助信息
參數說明:
-
-l 長列表. 打印關於鎖的附加信息,例如屬於java.util.concurrent 的 ownable synchronizers列表.
-
-F 當’jstack [-l] pid’沒有相應的時候強制打印棧信息
-
-m 打印java和native c/c++框架的所有棧信息.
-
-h | -help 打印幫助信息
pid 需要被打印配置信息的java進程id,可以用jps查詢.
Jstack 使用
通過使用 jps 命令獲取需要監控的進程的pid,然後使用 jstack pid 命令查看線程的堆棧信息。
通過 jstack 命令可以獲取當前進程的所有線程信息。
每個線程堆中信息中,都可以查看到 線程ID、線程的狀態(wait、sleep、running 等狀態)、是否持有鎖信息等。
命令行參數選項說明如下:
-l long listings,會打印出額外的鎖信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況
-m mixed mode,不僅會輸出Java堆棧信息,還會輸出C/C++堆棧信息(比如Native方法)
jstack可以定位到線程堆棧,根據堆棧信息我們可以定位到具體代碼,所以它在JVM性能調優中使用得非常多。下面我們來一個實例找出某個Java進程中最耗費CPU的Java線程並定位堆棧信息,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java進程ID,服務器上的Java應用名稱爲mrf-center:
root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep
root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到進程ID爲21711,第二步找出該進程內最耗費CPU的線程,可以使用
1)ps -Lfp pid
2)ps -mp pid -o THREAD, tid, time
3)top -Hp pid
用第三個,輸出如下:
TIME列就是各個Java線程耗費的CPU時間,CPU時間最長的是線程ID爲21742的線程,用
printf "%x\n" 21742
得到21742的十六進制值爲54ee,下面會用到。
OK,下一步終於輪到jstack上場了,它用來輸出進程21711的堆棧信息,然後根據線程ID的十六進制值grep,如下:
root@ubuntu:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait()
- CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),我找了下我的代碼,定位到下面的代碼:
// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
try {
if(!halted.get()) {
sigLock.wait(timeUntilContinue);
}
}
catch (InterruptedException ignore) {
}
}
它是輪詢任務的空閒等待代碼,上面的sigLock.wait(timeUntilContinue)就對應了前面的Object.wait()。
死鎖示例
下面通過一個例子,來演示 jstack 檢查死鎖的一個例子,代碼如下:
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
TimeUnit.SECONDS.sleep(1);
lock2.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
TimeUnit.SECONDS.sleep(1);
lock1.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.setName("mythread1");
t2.setName("mythread2");
t1.start();
t2.start();
}
使用 jstack -l pid 查看線程堆棧信息,發現在堆棧信息最後面檢查出了一個死鎖。如下圖
可以清楚的看出 mythread2 等待 這個鎖 “0x00000000d6eb82d0”,這個鎖是由於mythread1線程持有。
mythread1線程等待這個鎖“0x00000000d6eb8300”,這個鎖是由mythread2線程持有。
“mythread1”線程堆棧信息如下:
可以看出當前線程持有“0x00000000d6eb82d0”鎖,等待“0x00000000d6eb8300”的鎖
“mythread2”線程堆棧信息如下:
“mythread2”的堆棧信息中可以看出當前線程持有“0x00000000d6eb8300”鎖,等待“0x00000000d6eb82d0”的鎖。