JDK工具:jstack查看线程堆栈信息、排查问题

介绍

jstack(Java Stack Trace)是 JDK 自带的命令行工具,无需额外安装,核心作用是:

  • 导出指定 Java 进程的线程堆栈信息(包括线程状态、调用栈、锁持有情况等);
  • 排查线程相关问题:死锁、线程阻塞、死循环、线程卡死、接口响应慢等;
  • 适用于所有 Java 应用(Web 工程、微服务、桌面程序等),支持 Windows/Linux/Mac 全平台。

核心优势:轻量、无侵入、输出信息精准,是定位线程问题的“终极工具”(比图形化工具更深入)。

安装与环境准备

1. 安装

jstack 是 JDK 内置工具,只要安装了 JDK(版本≥1.5)就自带:

  • 检查 JDK 安装:打开 cmd(Windows)/终端(Linux),执行 java -version,能显示版本号即安装成功;
  • 找到 jstack 路径:JDK 安装目录下的 bin 文件夹(比如 Windows 下 C:\Program Files\Java\jdk1.8.0_301\bin\jstack.exe)。

2. 环境配置(可选,方便使用)

  • Windows:将 JDK 的 bin 目录添加到系统环境变量 PATH,即可在任意目录执行 jstack;
  • Linux/Mac:配置 JAVA_HOME 并将 $JAVA_HOME/bin 加入 PATH。

关键前置步骤:获取 Java 进程 PID

执行 jstack 前必须先拿到目标进程的 PID,使用 jps 命令:

# Windows/Linux/Mac 通用(JDK 自带)
jps -l
# 输出示例(PID 在前,进程名在后)
12345 org.apache.catalina.startup.Bootstrap  # Tomcat 进程,PID=12345
67890 com.xxx.SpringBootApplication          # Spring Boot 进程,PID=67890
  • Windows 额外方式:任务管理器 → 详细信息 → 找到 java.exe/javaw.exe,查看“PID”列;
  • Linux 额外方式:ps -ef | grep java。

用法

1. 基础命令格式

jstack [选项] <进程ID(PID)>

进程ID(PID):必须参数,指要排查的 Java 进程编号;

核心选项:

选项 作用
-F 强制导出堆栈(当进程挂起、无响应时使用)
-l 输出额外信息(比如锁的详细信息,排查死锁必加)
-m 输出混合堆栈(包含 Java 线程和本地方法(C/C++)堆栈,排查 JNI 问题用)
-h/-help 查看帮助信息

使用场景

场景1:导出线程堆栈(基础用法)

# 导出 PID=12345 的线程堆栈,输出到文件(方便查看)
jstack 12345 > thread.log

# 排查死锁时,加 -l 选项(输出锁信息)
jstack -l 12345 > thread_with_lock.log

# 进程无响应时,强制导出
jstack -F 12345 > thread_force.log
  • 导出的 thread.log 可用记事本、Notepad++、VS Code 等工具打开查看。

场景2:排查线程死锁(高频场景)

jstack -l <PID> 会自动检测死锁,并在堆栈末尾标注死锁信息,示例:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000016b4e668 (object 0x000000076b0081c0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000016b4c198 (object 0x000000076b0081d0, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.xxx.DeadLockDemo.run(DeadLockDemo.java:25)
        - waiting to lock <0x000000076b0081c0> (a java.lang.Object)
        - locked <0x000000076b0081d0> (a java.lang.Object)
"Thread-0":
        at com.xxx.DeadLockDemo.run(DeadLockDemo.java:15)
        - waiting to lock <0x000000076b0081d0> (a java.lang.Object)
        - locked <0x000000076b0081c0> (a java.lang.Object)

Found 1 deadlock.
  • 关键信息:死锁线程名、锁的对象、卡死的代码行(DeadLockDemo.java:25),可直接定位死锁代码。

场景3:排查线程卡死/阻塞(比如 Timer 任务耗时过长)

在导出的堆栈中搜索线程名(比如 Timer-2),查看线程状态和调用栈:

# 正常的 Timer 线程(TIMED_WAITING,等待执行)
"Timer-2" #20 daemon prio=5 os_prio=0 tid=0x000000001e8a8000 nid=0x4f4 waiting on condition [0x000000001f7ef000]
   java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x000000076b0081c0> (a java.util.TaskQueue)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.TimerThread.mainLoop(Timer.java:552)
    at java.util.TimerThread.run(Timer.java:505)

# 卡死的 Timer 线程(RUNNABLE,卡在业务方法)
"Timer-2" #20 daemon prio=5 os_prio=0 tid=0x000000001e8a8000 nid=0x4f4 RUNNABLE [0x000000001f7ef000]
   java.lang.Thread.State: RUNNABLE
    at com.xxx.MyTask.doBusiness(MyTask.java:25)  # 卡死在业务方法第25行
    at com.xxx.MyTask.run(MyTask.java:15)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
  • 关键判断:
    • 线程状态为 RUNNABLE 但长时间停在某个业务方法 → 方法执行卡死(死循环/耗时操作);
    • 线程状态为 BLOCKED → 等待获取锁,可能是锁竞争导致阻塞。

场景4:排查 CPU 100% 问题

  1. 用 top(Linux)/任务管理器(Windows)找到占用 CPU 最高的 Java 线程 ID(注意是线程ID,不是进程ID);
  2. 将线程 ID 转为十六进制(比如 1234 → 0x4D2);
  3. 用 jstack <进程PID> 导出堆栈,搜索十六进制线程 ID,定位对应的线程和方法:
    "Thread-10" #10 prio=5 os_prio=0 tid=0x000000001e8a8000 nid=0x4d2 RUNNABLE [0x000000001f7ef000]
    java.lang.Thread.State: RUNNABLE
    at com.xxx.CpuHighDemo.loop(CpuHighDemo.java:10)  # 死循环导致CPU 100%
    

线程状态解读

jstack 输出的线程状态是排查问题的关键,常见状态及含义:

线程状态 含义 是否正常 排查方向
RUNNABLE 运行中(执行代码逻辑) 正常/异常 长时间停留→死循环/耗时操作
TIMED_WAITING 定时等待(比如 Thread.sleep()、Timer 等待执行) 正常 无(这是线程休眠的正常状态)
WAITING 等待(比如 Object.wait()、LockSupport.park()) 正常 无(等待唤醒,非阻塞)
BLOCKED 阻塞(等待获取锁) 异常 锁竞争、死锁
TERMINATED 终止(线程已结束) 异常 线程被未捕获异常杀死

jstack 优缺点

优点

  1. 输出信息精准,能直接定位到代码行;
  2. 无侵入,执行命令不影响程序运行;
  3. 支持强制导出(进程无响应时仍可用);
  4. 跨平台,Windows/Linux/Mac 用法一致。

缺点

  1. 命令行工具,需要手动分析堆栈内容(对新手不友好);
  2. 只能导出“某一时刻”的堆栈,无法监控线程状态变化;
  3. 输出内容可能较多,需要筛选关键信息。

总结

  1. jstack 是 JDK 内置的命令行线程排查工具,核心用于导出线程堆栈,定位死锁、线程卡死、阻塞、CPU 高占比等问题;
  2. 核心用法:jps -l 找 PID → jstack -l <PID> > thread.log 导出堆栈 → 分析线程状态和调用栈;
  3. 关键技巧:
    • 排查死锁加 -l 选项;
    • 进程无响应加 -F 选项;
    • 搜索线程名/十六进制线程 ID 快速定位问题线程;
  4. 配套使用:jstack 适合“静态排查”(某一时刻的问题),搭配 JConsole/VisualVM 可实现“动态监控+静态分析”。

简单说,jstack 是 Java 线程问题排查的“终极武器”,只要是线程相关的问题,几乎都能通过它找到根因。