概述

在本文中,我们将介绍几种获取Java应用程序的线程dump的方法。

线程dump是 Java 进程的所有线程状态的快照。每个线程的状态都通过stack trace来呈现,它显示了线程堆栈的内容。线程dump对于诊断问题很有用,因为它显示了线程的各种活动。线程dump是用纯文本编写的,因此我们可以将它们的内容保存到文件中,然后在文本编辑器中查看它们

在接下来的部分中,我们将通过多种工具和方法来生成线程dump。

使用JDK提供的工具

JDK 提供了几个可以捕获 Java 应用程序的线程dump的实用工具。所有实用工具都位于JDK 主目录内的bin文件夹下。因此,只要该目录在我们的系统路径中,我们就可以从命令行执行这些实用工具程序。

jstack

jstack是一个基于命令行的 JDK 实用工具程序,我们可以使用它来捕获线程dump。它通过获取进程的pid并在控制台中显示线程dump。或者,我们可以将其输出到文件中。

让我们看一下使用 jstack 捕获线程dump的基本命令语法:

jstack [-F] [-l] [-m] <pid>

所有flag都是可选的。让我们看看它们的含义:

  • -F选项强制线程dump;通常在当jstack pid没有响应时 (进程挂起) 使用较为方便
  • -l选项指定在堆中查找可拥有的同步器和锁
  • -m 选项除了打印 Java 堆栈帧外,还打印本机堆栈帧(C 和 C++)

接下来让我们捕获线程dump并将结果保存到文件:

jstack 17264 > /tmp/threaddump.txt

其中17264是java进程的pid, 可以通过jps命令获取

Java Mission Control

Java Mission Control (JMC) 是一个 GUI 工具,用于收集和分析来自 Java 应用程序的数据。我们启动 JMC 后,它会显示在本地机器上运行的 Java 进程列表。我们还可以通过 JMC 连接到远程 Java 进程。

我们可以右键单击java进程,然后单击“Start Flight Recording”选项。在此之后,Threads选项卡会显示线程dump:

jmc

jvisualvm

jvisualvm也是一个带有GUI的工具,可以让我们监控、排除故障和分析 Java 应用程序。他的界面很简单,非常直观且易于使用。

我们可以右键单击 Java 进程并选择“Thread Dump”选项,该工具将创建一个线程dump并在新选项卡中打开展示它:

JVisualVM

从 JDK 9 开始,Visual VM 不包含在 Oracle JDK 和 Open JDK 发行版中。因此,如果我们使用 Java 9 或更新版本,我们可以从 Visual VM 开源项目站点获取 JVisualVM 。

jcmd

jcmd是一种通过向 JVM 发送命令请求来工作的工具。虽然功能强大,但它不包含任何远程功能;我们必须在运行 Java 进程的同一台机器上使用它。

我们可以通过使用它的Thread.print命令并指定进程的pid来使用它来获取线程dump:

jcmd 17264 Thread.print

jconsole

我们可以通过使用jconsole查看每个线程的stack trace。如果我们打开jconsole 并连接到正在运行的 Java 进程, 我们可以点击Threads选项卡并找到每个线程的stack trace:

JConsole

小结

可以看到, JDK 提供了很多多方法可以捕获线程dump。让我们来总结一下他们:

  • jstack : 提供最快最简单的捕获线程dump的方法;但是,从 Java 8 开始可以使用更好的替代方案
  • jmc:是一个增强的 JDK 分析和诊断工具。分析工具的通病就是性能开销大, 但jmc最大限度地减少了性能开销
  • jvisualvm:是一个具有出色 GUI 控制台的轻量级开源分析工具
  • jcmd:非常强大,推荐用于 Java 8 及更高版本。用于多种用途的单一工具:捕获thread dump ( jstack )、heap dump ( jmap )、系统属性和命令行参数 (jinfo)
  • jconsole:可以让我们检查线程的stack trace

使用命令行

在企业应用服务器中,出于安全原因,只安装了 JRE。因此,我们不能使用上述的几个工具,因为它们是 JDK 提供的。但是,有多种命令行选项可以让我们轻松捕获线程dump。

kill -3 命令 (Linux/Unix)

在类 Unix 系统中捕获线程dump的最简单方法是通过kill 命令,我们可以使用该命令通过kill() 系统调用向进程发送信号。在这个用例中,我们将向它发送-3信号, 使用前面示例中的pid,让我们看看如何使用kill来获取线程dump:

kill -3 17264

这样,接收信号的 Java 进程将在标准输出上打印线程dump。

如果我们使用以下flag组合运行 Java 进程,那么它也会将线程dump写到指定文件中:

-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=~/jvm.log

现在,如果我们发送 -3 信号,除了在标准输出中打印之外,还会将线程dump保存在 ~/jvm.log 文件中。

Ctrl + Break (Windows)

在 Windows 操作系统中,我们可以使用CTRL和Break 组合键捕获线程dump。需要我们先到用于启动 Java 应用程序的控制台中,然后同时按下CTRL和Break 键。值得注意的是,在某些键盘上,Break键不可用。因此,在这种情况下,可以同时使用CTRL、SHIFT和Pause键来捕获线程dump。这两个命令都将线程dump打印到控制台。

使用ThreadMxBean

我们将在本文中讨论的最后一种方法: 使用JMX。我们将使用ThreadMxBean来捕获线程dump:

  private static String threadDump(boolean lockedMonitors, boolean lockedSynchronizers) {
        StringBuilder threadDump = new StringBuilder(System.lineSeparator());
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        for (ThreadInfo threadInfo : threadMXBean.dumpAllThreads(lockedMonitors, lockedSynchronizers)) {
            threadDump.append(threadInfo.toString());
        }
        return threadDump.toString();
    }

在上面的这个方法中,我们执行了几个步骤:

  1. 首先,初始化一个空的StringBuilder来保存每个线程的堆栈信息。
  2. 然后我们使用ManagementFactory类来获取ThreadMxBean 的实例。ManagementFactory 是一个工厂类,用于获取 Java 平台管理的 bean。另外,ThreadMxBean 是JVM线程系统的管理接口。
  3. 如果将lockedMonitors和lockedSynchronizers值设置为true, 表示捕获线程dump中的可拥有同步器和所有锁定的监视器。

总结

在本文中,我们讨论了多种捕获线程dump的方法。首先,我们讨论了各种 JDK提供的工具,然后是命令行替代方案。最后,我们使用了 JMX 的编程式方法来获取线程dump。希望这篇文章可以在工作和学习中帮助到你😀。