Jstack 概述
Jstack 工具是 Java 虚拟机自带的一种堆栈跟踪工具。Jstack 用于生成或导出(DUMP)JVM 虚拟机运行实例当前时刻的线程快照。线程快照是当前JVM实例内每一个线程正在执行的方法堆栈的集合,生成或导出线程快照的主要目的是定位线程出现长时间运行、停顿或者阻塞的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。线程出现停顿的时候通过 Jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
Jstack 命令的语法格式如下:
jstack <pid> // pid 表示 Java 进程 id,可以使用 jsp 命令查看
一般情况下,通过 Jstack 输出的线程信息主要包括:JVM 线程、用户线程等。其中,JVM 线程在 JVM 启动时就存在,主要用于执行譬如垃圾回收、低内存的检测等后台任务,这些线程往往在 JVM 初始化的时候就存在。而用户线程则是在程序创建了新的线程时才会生成。这里需要注意的是:
- 在实际运行中,往往一次 DUMP 的信息不足以确认问题。建议产生三次 DUMP 信息,如果每次 DUMP 都指向同一个问题,我们才能确定问题的典型性
- 不同的 Java 虚拟机的线程导出来的 DUMP 信息格式是不一样的,并且同一个 JVM 的不同版本,DUMP 信息也有差别
Jstack 输出的信息
使用 Jstack 获取线程状态信息时,获取到的数据如下所示:
GC task thread
为垃圾回收线程,此类线程会负责进行垃圾回收。通常 JVM 会启动多个 GC 线程,在 GC 线程的名称中,#
后面的数字会累加,如 GC taskthread#1、GC task thread#2 等VM Periodic Task Thread
线程是 JVM 周期性任务调度的线程,该线程在 JVM 内使用得比较频繁,比如定期的内存监控、JVM 运行状况监控tid
:线程实例在 JVM 进程中的 idnid
:线程实例在操作系统中对应的底层线程的线程 idprio
:线程实例在 JVM 进程中的优先级os_prio
:线程实例在操作系统中对应的底层线程的优先级线程状态
:如 runnable、waiting on condition 等
Jstack 查看线程 sleep 状态
先看下面的示例:
public class SleepTest {
// 睡眠时长:5s
public static final int SLEEP_GAP = 5000;
// 睡眠次数,值大一点方便使用 Jstack
public static final int MAX_TURN = 50;
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new SleepThread();
thread.start();
}
System.out.println(Thread.currentThread() + " 运行结束");
}
static class SleepThread extends Thread {
static int threadSeqNumber = 1;
public SleepThread() {
super("SleepThread-" + threadSeqNumber);
threadSeqNumber++;
}
@Override
public void run() {
try {
for (int i = 0; i < MAX_TURN; i++) {
System.out.println(getName() + ",睡眠次数:" + i);
Thread.sleep(SLEEP_GAP);
}
} catch (Exception e) {
System.out.println(getName() + " 发生异常中断");
}
System.out.println(getName() + " 运行结束");
}
}
}
在代码运行之后,打开终端,键入 jps
命令查看进程信息,其中, SleepTest
进程 的 pid
为 38376
:
> jps
34880 Launcher
22036 DeadLock
40452 Jps
38376 SleepTest
17180
通过 jstack 命令查看进程信息:
> jstack 38376
2021-07-12 23:13:21
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode):
"DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x0000000002af3000 nid=0x6e5c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"SleepThread-5" #15 prio=5 os_prio=0 tid=0x0000000018b71000 nid=0x9430 waiting on condition [0x0000000019b6f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bujian.concurrence.SleepTest$SleepThread.run(SleepTest.java:34)
"SleepThread-4" #14 prio=5 os_prio=0 tid=0x0000000018b70800 nid=0x9a00 waiting on condition [0x0000000019a6f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bujian.concurrence.SleepTest$SleepThread.run(SleepTest.java:34)
"SleepThread-3" #13 prio=5 os_prio=0 tid=0x0000000018b6d800 nid=0x9cfc waiting on condition [0x000000001996f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bujian.concurrence.SleepTest$SleepThread.run(SleepTest.java:34)
"SleepThread-2" #12 prio=5 os_prio=0 tid=0x0000000018b6b000 nid=0x4248 waiting on condition [0x000000001986f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bujian.concurrence.SleepTest$SleepThread.run(SleepTest.java:34)
"SleepThread-1" #11 prio=5 os_prio=0 tid=0x0000000018b6a000 nid=0x9318 waiting on condition [0x000000001976f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.bujian.concurrence.SleepTest$SleepThread.run(SleepTest.java:34)
......
"VM Thread" os_prio=2 tid=0x00000000176d7800 nid=0x9e0c runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002b08800 nid=0xa6c8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002b0a800 nid=0xa0a4 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002b0c000 nid=0x9b4c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002b0d800 nid=0x4cd8 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000018b5d000 nid=0x753c waiting on condition
JNI global references: 12
通过以上的 Jstack 指令输出,可以看到在进行线程 DUMP 的时间点,所创建的 5 个 SleepThread 线程都处于 TIMED_WAITING(sleeping)
状态。当线程睡眠时间满后,线程不一定会立即得到执行,因为此时 CPU 可能正在执行其他的任务,线程首先进入就绪状态,等待分配 CPU 时间片以便有机会执行。