JVM——使用工具——jstack

概述

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”的鎖。

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