當 Java 應用出現「卡死」、「響應極慢」、「CPU 飆高」等疑難雜症時,JDK 自帶的 jstack 是首選現場診斷工具。本文通過四大經典故障場景實戰,演示如何利用線程快照秒級定位問題代碼行,掌握生產環境故障排查核心技能。
# 方式一:jps(推薦,顯示主類名)
jps -l
# 7588 concurrency.Test
# 方式二:tasklist | findstr java(Windows)
tasklist | findstr java
# javaw.exe 7588 Console 1 123,456 K
jstack 7588 > threaddump.txt
# 或直接控制台查看
jstack 7588
進階參數:-l 顯示鎖信息、-m 混合模式(含 Native 堆棧)、-F 強制導出(進程掛起時)
package concurrency;
public class Test {
public static void main(String[] args) throws InterruptedException {
while (true) { } // 第 8 行
}
}
"main" #1 prio=5 os_prio=0 tid=0x0000000002a9b000 nid=0x1da4 runnable [0x0000000002b9e000]
java.lang.Thread.State: RUNNABLE
at concurrency.Test.main(Test.java:8)
Test.java:8 正是 while(true) 行sleep/yield、業務邏輯是否預期長時間運行class TestTask implements Runnable {
public void run() {
synchronized (this) {
try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class Test {
public static void main(String[] args) {
Executors.newFixedThreadPool(1).execute(new TestTask());
}
}
"pool-1-thread-1" #11 prio=5 os_prio=0 tid=0x000000001e2c3800 nid=0x1f4c in Object.wait() [0x000000001f5fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at concurrency.TestTask.run(Test.java:13)
- locked <0x00000000d5f8a1b0> (a concurrency.TestTask)
Test.java:13 即 wait() 調用行<0x00000000d5f8a1b0> 為 TestTask 實例notify/notifyAll、鎖對象是否一致、是否應使用 wait(timeout) 避免無限等待class TestTask implements Runnable {
private Object obj1, obj2;
private int order; // 0 或 1,控制加鎖順序相反
public void test1() { synchronized(obj1) { Thread.yield(); synchronized(obj2) { ... } } }
public void test2() { synchronized(obj2) { Thread.yield(); synchronized(obj1) { ... } } }
public void run() {
while(true) { if(order==1) test1(); else test2(); }
}
}
public class Test {
public static void main(String[] args) {
Object obj1 = new Object(), obj2 = new Object();
ExecutorService ex = Executors.newFixedThreadPool(10);
for(int i=0;i<10;i++) ex.execute(new TestTask(i%2==0?1:0, obj1, obj2));
}
}
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000002b3c3e8 (object 0x00000000d5f8a220, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000002b3c438 (object 0x00000000d5f8a250, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at concurrency.TestTask.test2(Test.java:45)
- waiting to lock <0x00000000d5f8a220> (a java.lang.Object)
- locked <0x00000000d5f8a250> (a java.lang.Object)
at concurrency.TestTask.run(Test.java:58)
"Thread-0":
at concurrency.TestTask.test1(Test.java:35)
- waiting to lock <0x00000000d5f8a250> (a java.lang.Object)
- locked <0x00000000d5f8a220> (a java.lang.Object)
at concurrency.TestTask.run(Test.java:58)
Found 1 deadlock.
obj1、obj2),形成循環等待TestTask.test1() 第 35 行、test2() 第 45 行ReentrantLock.tryLock(timeout)、縮小同步塊範圍public class Test {
public static void main(String[] args) throws IOException {
InputStream is = System.in;
int i = is.read(); // 第 12 行,等待用戶輸入
System.out.println("exit。");
}
}
"main" #1 prio=5 os_prio=0 tid=0x0000000002a9b000 nid=0x1da4 runnable [0x0000000002b9e000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at concurrency.Test.main(Test.java:12)
Test.java:12 即 System.in.read()FileInputStream.readBytes 表明進入 OS 系統調用| 線程狀態 | 典型代碼 | 堆棧特徵 | 排查方向 |
|---|---|---|---|
| BLOCKED | synchronized 進入阻塞 | waiting for monitor entry | 鎖競爭激烈、長耗時同步塊 |
| WAITING | Object.wait()、Thread.join()、LockSupport.park() | in Object.wait()、parking | 缺少 notify、超時設置、死鎖 |
| TIMED_WAITING | Thread.sleep()、wait(timeout)、join(timeout)、parkNanos | sleeping、waiting with timeout | 業務邏輯正常休眠、超時保護 |
| RUNNABLE (I/O) | Socket.read、FileInputStream.read、System.in.read | Native Method、readBytes | 設置 SO_TIMEOUT、NIO 非阻塞、異步 I/O |
jstack,對比堆棧變化,區分「瞬間慢」與「持續卡住」GC、Compiler、Finalizer)-l:jstack -l pid 顯示 java.util.concurrent.Lock 等顯式鎖信息top -H -p pid:Linux 下將高 CPU 線程 ID 轉十六進制(printf '%x\n' tid),與 jstack 中 nid=0x... 對應,精準定位「燒 CPU」代碼jstack 是 Java 開發者的「聽診器」。掌握四大經典場景(死循環、無限等待、死鎖、I/O 阻塞)的堆棧特徵識別,配合多次抽樣、CPU 線程對應、鎖信息增強等實戰技巧,能將故障定位時間從「小時級」壓縮到「分鐘級」。建議每位後端工程師熟練掌握線程快照閱讀能力,將其納入日常技能體系,從「會用框架」進階到「懂運行機制」。