Java 程序員必備:jstack 命令解析

前言

如果有一天,你的 Java 程序長時間停頓,也許是它病了,需要用 jstack 拍個片子分析分析,才能診斷具體什麼病症,是死鎖綜合徵,還是死循環等其他病症,本文我們一起來學習 jstack 命令~

jstack 的功能

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

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

jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP

最常用的是

jstack [option] <pid>  // 打印某個進程的堆棧信息

option 參數說明如下:

線程狀態等基礎回顧

線程狀態簡介

jstack 用於生成線程快照的,我們分析線程的情況,需要複習一下線程狀態吧,拿小凳子坐好,複習一下啦~

Java 語言定義了 6 種線程池狀態:

Dump 文件的線程狀態一般其實就以下 3 種:

Monitor 監視鎖

因爲 Java 程序一般都是多線程運行的,Java 多線程跟監視鎖環環相扣,所以我們分析線程狀態時,也需要回顧一下 Monitor 監視鎖知識。

Monitor 的工作原理圖如下:

Dump 文件分析關注重點

實戰案例 1: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 程序

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

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

由上圖,可以清晰看到死鎖信息:

還原死鎖真相

“mythread-jay" 線程堆棧信息分析如下:

實戰案例 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. 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)對比,更準確嘛~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/OGqWzj-QcFvoUgiDQ-MVsw