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 {// 睡眠时长:5spublic static final int SLEEP_GAP = 5000;// 睡眠次数,值大一点方便使用 Jstackpublic 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++;}@Overridepublic 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:
> jps34880 Launcher22036 DeadLock40452 Jps38376 SleepTest17180
通过 jstack 命令查看进程信息:
> jstack 383762021-07-12 23:13:21Full 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 conditionJNI global references: 12
通过以上的 Jstack 指令输出,可以看到在进行线程 DUMP 的时间点,所创建的 5 个 SleepThread 线程都处于 TIMED_WAITING(sleeping) 状态。当线程睡眠时间满后,线程不一定会立即得到执行,因为此时 CPU 可能正在执行其他的任务,线程首先进入就绪状态,等待分配 CPU 时间片以便有机会执行。
