JVM——jstack命令


 

概述

jstack是JVM自帶的Java堆棧跟蹤工具,它用於打印出給定的java進程ID、core file、遠程調試服務的Java堆棧信息,它可以非常方便的做java進程的thread dump。

 

一、jstack 介紹

jstack 功能

  • jstack命令用於生成虛擬機當前時刻線程快照
  • 線程快照是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的定位線程出現長時間停頓的原因, 如線程間死鎖、死循環、請求外部資源導致的長時間等待等問題。
  • 線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在后台做什么事情,或者等待什么資源。
  • 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。
  • 另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。

 

jstack 用法

[root@push ~]# jstack -help
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message
  • -F: 當正常輸出的請求不被響應時,強制輸出線程堆棧
  • -m: 如果調用到本地方法的話,可以顯示C/C++的堆棧
  • -l: 除堆棧外,顯示關於鎖的附加信息,在發生死鎖時可以用jstack -l pid來觀察鎖持有情況
  • -h: 打印幫助信息

 

示例一:no option

命令:jstack <pid>

描述:打印堆棧信息在控制台。

 

示例二:no option

命令:jstack <pid> > <pid>_core.dump

描述:打印堆棧信息並輸出到文件。

 

示例三:-F

命令:jstack -F <pid>

描述:當進程掛起(hung)時,上面的命令可能沒有響應,這時需要使用 "-F" 參數來強制執行thread dump。

 

示例四:-l

命令:jstack -l <pid>

描述:顯示關於鎖的附加信息,在發生死鎖時可以用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監視鎖知識。

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排查死鎖?

1、什么是死鎖?

死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法進行下去。

2、如何用如何用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都是只執行到一半,就陷入了阻塞等待狀態~

3、jstack排查Java死鎖步驟

  • 在終端中輸入jps查看當前運行的java程序
  • 使用 jstack -l pid 查看線程堆棧信息
  • 分析堆棧信息

3.1、在終端中輸入jps查看當前運行的java程序

通過使用 jps 命令獲取需要監控的進程的pid,我們找到了 23780 DeathLockTest

3.2、使用 jstack -l pid 查看線程堆棧信息

由上圖,可以清晰看到 死鎖信息:
  • mythread-tianluo 等待這個鎖 “0x00000000d61ae3a0”,這個鎖是由於mythread-jay線程持有。
  • mythread-jay線程等待這個鎖“0x00000000d61ae3d0”,這個鎖是由mythread-tianluo 線程持有。

3.3、還原死鎖真相

“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過高步驟

  1. 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. pid轉換成16進制值

我們把占用cpu資源較高的線程pid(本例子是21350),將該pid轉成16進制的值。

[root@push ~]# printf '%x\n' 21350
5366

4. jstack pid

通過top命令定位到cpu占用率較高的線程之后,接着使用jstack pid命令來查看當前java進程的堆棧狀態,jstack 21350后,內容如下:

jstack 21350

也可以用以下命令直接過濾:

jstack 21350 | grep 5366

5. jstack -l [PID] >/tmp/log.txt

其實,前3個步驟,堆棧信息已經出來啦。但是一般在生成環境,我們可以把這些堆棧信息打到一個文件里,再回頭仔細分析哦~

6. 分析堆棧信息

在thread dump中,每個線程都有一個nid,我們找到對應的nid(5366),發現一直在跑(24行)

這個時候,可以去檢查代碼是否有問題啦~ 當然,也建議隔段時間再執行一次stack命令,再一份獲取thread dump,畢竟兩次拍片結果(jstack)對比,更准確嘛~

 

 

引用:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM