給你的Java程序拍個片子吧:jstack命令解析

前言

若是有一天,你的Java程序長時間停頓,也許是它病了,須要用jstack拍個片子分析分析,才能診斷具體什麼病症,是死鎖綜合徵,仍是死循環等其餘病症,本文咱們一塊兒來學習jstack命令~java

  • jstack 的功能
  • jstack用法
  • 線程狀態等基礎回顧
  • 實戰案例1:jstack 分析死鎖
  • 實戰案例2:jstack 分析CPU 太高

jstack 的功能

jstack是JVM自帶的Java堆棧跟蹤工具,它用於打印出給定的java進程ID、core file、遠程調試服務的Java堆棧信息.git

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 命令格式以下github

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種線程池狀態:jvm

  • New:建立後還沒有啓動的線程處於這種狀態,不會出如今Dump中。
  • RUNNABLE:包括Running和Ready。線程開啓start()方法,會進入該狀態,在虛擬機內執行的。
  • Waiting:無限的等待另外一個線程的特定操做。
  • Timed Waiting:有時限的等待另外一個線程的特定操做。
  • 阻塞(Blocked):在程序等待進入同步區域的時候,線程將進入這種狀態,在等待監視器鎖。
  • 結束(Terminated):已終止線程的線程狀態,線程已經結束執行。

Dump文件的線程狀態通常其實就如下3種:jsp

  • RUNNABLE,線程處於執行中
  • BLOCKED,線程被阻塞
  • WAITING,線程正在等待

Monitor 監視鎖

由於Java程序通常都是多線程運行的,Java多線程跟監視鎖環環相扣,因此咱們分析線程狀態時,也須要回顧一下Monitor監視鎖知識。ide

有關於線程同步關鍵字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,咱們找到了23780 DeathLockTest

使用 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太高步驟

    1. top
    1. top -Hp pid
    1. jstack pid
    1. jstack -l [PID] >/tmp/log.txt
    1. 分析堆棧信息

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進程的堆棧狀態,jstack 21350後,內容以下:

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

其實,前3個步驟,堆棧信息已經出來啦。可是通常在生成環境,咱們能夠把這些堆棧信息打到一個文件裏,再回頭仔細分析哦~

5. 分析堆棧信息

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


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

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

參考與感謝

我的公衆號

  • 以爲寫得好的小夥伴給個點贊+關注啦,謝謝~
  • 若是有寫得不正確的地方,麻煩指出,感激涕零。
  • 同時很是期待小夥伴們可以關注我公衆號,後面慢慢推出更好的乾貨~嘻嘻
  • github地址:https://github.com/whx123/JavaHome
相關文章
相關標籤/搜索