jstack是JDK内置的命令行工具,主要用于生成Java虚拟机当前时刻的线程快照,通过线程快照可以查看每个线程的执行状态、持有的锁信息以及调用栈路径,是排查Java应用线程相关问题的常用工具。

jstack工具基本使用
使用jstack前需要先获取目标Java进程的PID,可以通过jps命令快速查看当前运行的Java进程列表,命令如下:
# 查看所有Java进程PID和主类名 jps -l
获取到PID后,执行jstack命令导出线程快照,基础用法为:
# 导出线程快照到指定文件 jstack 进程PID > jstack_log.txt
如果需要查看更详细的锁信息,可以添加-l参数:
jstack -l 进程PID > jstack_detail_log.txt
使用jstack排查Java死锁问题
死锁是指两个或多个线程互相持有对方需要的锁,导致所有线程都无法继续执行的情况。jstack输出的日志中会自动检测并标记死锁信息,排查步骤如下:
步骤1:触发死锁场景
以下是一个简单的死锁示例代码,两个线程互相持有对方需要的锁:
public class DeadLockDemo {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();
public static void main(String[] args) {
// 线程1先获取LOCK_A,再尝试获取LOCK_B
new Thread(() -> {
synchronized (LOCK_A) {
System.out.println("线程1获取到LOCK_A");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK_B) {
System.out.println("线程1获取到LOCK_B");
}
}
}, "Thread-1").start();
// 线程2先获取LOCK_B,再尝试获取LOCK_A
new Thread(() -> {
synchronized (LOCK_B) {
System.out.println("线程2获取到LOCK_B");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK_A) {
System.out.println("线程2获取到LOCK_A");
}
}
}, "Thread-2").start();
}
}
步骤2:导出线程快照分析
运行上述代码后,通过jps获取进程PID,执行jstack导出日志,在日志末尾会自动出现死锁提示:
Found one Java-level deadlock: ============================= "Thread-2": waiting to lock monitor 0x00007f8a38006258 (object 0x000000076abf8c98, a java.lang.Object), which is held by "Thread-1" "Thread-1": waiting to lock monitor 0x00007f8a38007268 (object 0x000000076abf8ca8, a java.lang.Object), which is held by "Thread-2" Java stack information for the threads listed above: =================================================== "Thread-2": at DeadLockDemo.lambda$main$1(DeadLockDemo.java:30) - waiting to lock <0x000000076abf8c98> (a java.lang.Object) - locked <0x000000076abf8ca8> (a java.lang.Object) "Thread-1": at DeadLockDemo.lambda$main$0(DeadLockDemo.java:16) - waiting to lock <0x000000076abf8ca8> (a java.lang.Object) - locked <0x000000076abf8c98> (a java.lang.Object) Found 1 deadlock.
从日志中可以明确看到两个线程的等待锁和持有锁信息,以及死锁发生的具体代码行,直接定位到对应的代码逻辑修改即可解决死锁问题。
使用jstack排查CPU飙升问题
当Java应用CPU占用过高时,通常是有线程在执行大量计算或者死循环逻辑,排查步骤如下:
步骤1:定位高CPU占用的线程
首先通过top命令查看进程的资源占用情况,找到CPU占用高的Java进程PID,然后执行以下命令查看该进程下所有线程的CPU占用:
# 查看进程下线程CPU占用,按CPU排序 top -Hp 进程PID
记录下CPU占用最高的线程ID,将线程ID转换为16进制,因为jstack日志中的线程ID是16进制格式:
# 将10进制线程ID转为16进制,比如线程ID是12345,执行以下命令 printf "%xn" 12345
步骤2:在jstack日志中定位线程
导出jstack日志后,搜索转换后的16进制线程ID,找到对应的线程信息,查看线程的调用栈:
"HighCpuThread" #12 prio=5 os_prio=0 tid=0x00007f8a48013800 nid=0x3039 runnable [0x00007f8a3c5f7000] java.lang.Thread.State: RUNNABLE at CpuHighDemo.calculate(CpuHighDemo.java:15) at CpuHighDemo.lambda$main$0(CpuHighDemo.java:8) at CpuHighDemo$$Lambda$1/0x0000000800b91440.run(Unknown Source) at java.lang.Thread.run(Thread.java:748)
根据调用栈中的代码行,定位到具体的业务逻辑,比如是否存在死循环、大量重复计算等问题,针对性优化即可。
jstack使用注意事项
- jstack需要和目标Java进程的用户权限一致,否则可能无法导出快照
- 频繁执行jstack可能会对应用性能产生轻微影响,建议在问题复现时按需执行
- 如果应用使用了线程池,需要注意线程的命名,方便快速定位业务线程
- 对于容器化部署的Java应用,需要在容器内部执行jstack命令,或者挂载宿主机的JDK路径使用