【Java可执行命令】(二十一)线程快照生成工具 jstack:帮助开发人员分析和排查线程相关问题(死锁、死循环、线程阻塞...)-LMLPHP


1️⃣ 概念

jstack 命令是 Java Development Kit(JDK)中提供的一项诊断工具,用于生成Java虚拟机(JVM)的线程快照。它的主要设计目的是帮助开发人员分析和排查应用程序中的线程相关问题,如死锁、死循环、线程阻塞等。

线程快照是指在某个时间点上,记录下所有运行在JVM中的线程的状态信息,包括线程栈、线程ID、线程所属的进程ID等。通过线程快照,我们可以了解线程执行状态、调用堆栈、线程的持有锁、等待资源等详细信息。

jstack 命令工具有以下特征:

  • 生成文本文件:jstack 命令生成的线程快照以文本文件形式输出,方便存储和后续分析;
  • 多种输出格式:jstack 提供了多种输出格式,如16进制、16进制元数据等,以满足不同场景下的需求;
  • 线程状态信息:线程快照中包含了每个线程的状态信息,如WAITINGTIMED_WAITINGBLOCKED等;
  • 堆栈追踪:通过解析线程的栈帧(Stack Frame),可以查看线程执行的方法调用轨迹。这对于发现死锁或代码性能调优至关重要。

jstack的原理实际是通过Java虚拟机提供的ThreadMXBean 接口获取线程相关信息,并将获取到的信息打印到控制台或文本文件中。它会遍历JVM中的所有线程,然后获取线程堆栈信息和其他状态信息,最终输出线程快照。

2️⃣ 优势和缺点

优点:

  • jstack命令能够提供关键的线程信息,方便开发人员快速定位和诊断线程相关问题;
  • JDK自带的工具,无需额外配置,使用方便;
  • 命令执行速度较快,在诊断简单问题方面效果良好。

缺点:

  • jstack只提供静态的线程快照,不能实时监控和连续跟踪线程,在某些复杂场景下可能不够全面。

3️⃣ 使用

3.1 语法格式

jstack 命令的使用语法如下:

Usage:
    jstack [-l] <pid>
        (to connect to running process)(连接到正在运行的进程)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)(连接到挂起的进程)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)(连接到核心文件)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)(连接到远程调试服务器)

其中,[-l] -F[-m] 都是可选的一些命令参数选项,<pid> 是Java进程的进程ID。对于命令中可选参数汇总如下:

需要注意以下几点:

  • 在使用jstack命令生成线程快照时,目标Java进程可能会暂停执行,影响应用程序的性能;
  • 对于生产环境的Java进程,建议在非高峰期或业务低谷时使用 jstack命令,以减少对正常业务流程的干扰;
  • 考虑保密性:线程快照可能包含敏感信息(如部分代码逻辑),请确保妥善处理线程快照文件以防止泄露。

3.2 使用步骤及技巧

  • 找到目标进程的 PID:可以通过jps命令来查看Java进程的进程ID;
  • 执行命令:在命令行中输入jstack <pid>,即可生成线程快照;
  • 分析线程信息:打开生成的线程快照文本文件,查看线程状态、堆栈追踪等信息;
  • 利用线程ID定位问题:根据线程ID可以在堆栈追踪中定位对应的代码和资源问题,帮助进行问题诊断。

对于大规模线程的应用程序,可以使用多次执行jstack <pid>并比较不同快照的结果,以检测潜在的线程问题。
同时也可以结合其他工具(如jvisualvm)使用,可以更全面地分析和调试线程相关问题。

3.3 使用案例

(1)通过jps命令来查看Java进程的进程ID;

c:\Users\xxx\IdeaProjects\untitled15\src\com\xiaoshan>jps
15216 TestDemo
166976 Launcher
172008 Jps
172716

(2)在命令行中输入jstack ,生成线程快照;

c:\Users\xxx\IdeaProjects\untitled15\src\com\xiaoshan>jstack 15216

执行结果:

2023-08-09 15:19:52
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x00000000193c8800 nid=0x2a5d4 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000193a8000 nid=0x28a78 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001939e800 nid=0x2a108 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001939e000 nid=0x29dd0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019399800 nid=0x25afc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000193a0000 nid=0x29528 runnable [0x000000001a9be000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d6688070> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d6688070> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:49)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000019353000 nid=0x28dbc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019300000 nid=0x2a1b8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017c23800 nid=0x2a5b8 in Object.wait() [0x000000001a64f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6508ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000d6508ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000192e3000 nid=0x2a504 in Object.wait() [0x000000001a54f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6506bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d6506bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x0000000002a63000 nid=0x2a1bc runnable [0x00000000024be000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileInputStream.readBytes(Native Method)
        at java.io.FileInputStream.read(FileInputStream.java:255)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
        - locked <0x00000000d655a9c0> (a java.io.BufferedInputStream)
        at com.xiaoshan.test.TestDemo.main(TestDemo.java:38)

"VM Thread" os_prio=2 tid=0x0000000017c16800 nid=0x1414 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002a78800 nid=0x27c9c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002a7a000 nid=0x296a0 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002a7b800 nid=0x2a498 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002a7d000 nid=0x2a304 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002a7f800 nid=0x470 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002a81800 nid=0x2794c runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002a85000 nid=0x5450 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002a86000 nid=0x47bc runnable

"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002a87000 nid=0x29ec0 runnable

"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002a88800 nid=0x2a624 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x00000000193c9000 nid=0x29c78 waiting on condition

JNI global references: 12


此结果是Java虚拟机线程转储(Full thread dump)的输出。它提供了一个关于在Java程序中正在运行的所有线程的信息快照。以下是每个线程的解释:

  1. "Service Thread":服务线程,是用于执行后台任务的线程。
  2. "C1 CompilerThread3":即时编译器线程,负责编译Java字节码为本地代码,以提高执行效率。
  3. "C2 CompilerThread2":另一个即时编译器线程,也负责编译Java字节码为本地代码。
  4. "C2 CompilerThread1":即时编译器线程的另一个实例。
  5. "C2 CompilerThread0":即时编译器线程的另一个实例。
  6. "Monitor Ctrl-Break":监控Ctrl-Break事件的线程,可用于在IDE中终止程序执行。
  7. "Attach Listener":监听Java虚拟机附加事件的线程。
  8. "Signal Dispatcher":信号分发线程,用于处理操作系统发送的信号。
  9. "Finalizer":Finalizer线程,负责执行对象的finalize()方法(垃圾回收前的清理工作)。
  10. "Reference Handler":引用处理线程,用于处理Reference类型的对象。
  11. "main":主线程,程序的入口点。
  12. "VM Thread":虚拟机线程,执行必要的虚拟机任务。
  13. "GC task thread#0 (ParallelGC)""GC task thread#9 (ParallelGC)":并行垃圾回收(ParallelGC)线程。在此例中,有10个线程用于并行垃圾回收。
  14. "VM Periodic Task Thread":周期性任务线程,定期执行虚拟机内部的一些任务。

最后一行指示JNI全局引用的数量为12,这是Java Native Interface(JNI)使用的内存资源。

以"Service Thread"线程为例,详细解释一个线程的输出信息:

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x00000000193c8800 nid=0x2a5d4 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
  • 线程名称:“Service Thread”;
  • 线程编号(Thread ID):0x00000000193c8800
  • Native ID(nid):0x2a5d4
  • 守护线程标识(daemon):守护线程(daemon),这意味着当所有非守护线程结束时,该线程会被自动终止;
  • 线程优先级(priority):9,具有10个等级的优先级范围(1为最低,10为最高);
  • 操作系统优先级(os_priority):0,表示该线程在操作系统中使用默认的优先级设置;
  • 线程特定标识符(tid):0x00000000193c8800
  • 线程状态(java.lang.Thread.State):RUNNABLE,表示该线程当前正在运行或准备运行。

总而言之,该输出提供了关于Java程序运行时各个线程的详细信息,包括线程名称、优先级、状态以及线程正在执行的代码位置(如果可获得)。

4️⃣ 应用场景

  • 线程死锁定位:通过观察线程快照中的锁信息,可以排查应用程序中的死锁问题;
  • 程序性能分析:通过分析线程状态和堆栈信息,可以发现耗时操作、线程阻塞等导致程序性能下降的原因;
  • 代码调优:通过观察方法调用轨迹,可以确定是否存在效率低下的代码段,进而进行优化。

🌾 总结

jstack 命令是Java开发人员调试和分析线程相关问题时常用的工具。它能够生成线程快照,提供有关线程状态和堆栈追踪的重要信息,帮助开发人员定位线程问题、代码性能问题以及资源争用问题。尽管其输出是静态的,不能实时监控线程状态,但是在处理简单线程问题时具有较高的效果。

了解jstack的使用方法、特征、优化技巧和注意事项,能够提升开发人员在排查线程问题方面的能力,进一步提升应用程序的性能和稳定性。

【Java可执行命令】(二十一)线程快照生成工具 jstack:帮助开发人员分析和排查线程相关问题(死锁、死循环、线程阻塞...)-LMLPHP
08-09 20:00