性能文章>【译】什么是线程dump文件,我们又该如何分析?>

【译】什么是线程dump文件,我们又该如何分析?转载

2月前
275015

让我们谈谈线程转储,以及如何分析它。

我们还将讨论它如何帮助查明问题以及您可以使用的一些分析器。

什么是线程?

进程是加载到计算机内存中并正在执行的计算机程序。它可以由一个处理器或一组处理器执行。进程在内存中被描述为具有重要信息,例如变量存储、文件句柄、程序计数器、寄存器和信号等。

一个进程可以由许多称为线程的轻量级进程组成。这有助于实现并行性,其中一个进程被划分为多个线程。这会带来更好的性能。一个进程中的所有线程共享相同的内存空间并相互依赖。

线程转储(Thread Dumps

当进程正在执行时,我们可以使用线程转储来检测进程中线程的当前执行状态。线程转储包含程序执行期间在特定时间点处于活动状态的所有线程的快照。它包含有关线程及其当前状态的所有相关信息。

今天的现代应用程序涉及多个线程。每个线程都需要某些资源,执行与进程相关的某些活动。这可以提高应用程序的性能,因为线程可以利用可用的 CPU 内核。

但是有一些权衡,例如,有时多个线程可能无法很好地相互协调,并且可能会出现死锁情况。因此,如果出现问题,我们可以使用线程转储来检查线程的状态。

Java中的线程转储

JVM 线程转储是在特定时间点作为进程一部分的所有线程的状态列表。它包含有关线程堆栈的信息,以堆栈跟踪的形式呈现。由于它是明文编写的,因此可以保存内容以供以后查看。线程转储分析有助于

  • 优化 JVM 性能
  • 优化应用程序性能
  • 诊断问题,例如死锁、线程争用等。

线程dump文件如何生成

有很多方法可以生成线程转储。以下是一些基于 JVM 的工具,可以从命令行/终端(CLI 工具)或 Java 安装文件夹的/bin(GUI 工具)目录执行。

让我们来探索一下。

1、jStack

生成线程转储的最简单方法是使用 jStack。jStack 随 JVM 一起提供,可以从命令行使用。在这里,我们需要要为其生成线程转储的进程的 PID。要获取 PID,我们可以使用jps命令,如下所示。

jps -l

jps列出所有 Java 进程 ID。

在 Windows 上

C:\Program Files\Java\jdk1.8.0_171\bin>jps -l
47172 portal
6120 sun.tools.jps.Jps
C:\Program Files\Java\jdk1.8.0_171\bin>

在 Linux 上

[geekfkare@localhost ~]# jps -l
1088 /opt/keycloak/jboss-modules.jar
26680 /var/lib/jenkins/workspace/kyc/kyc/target/kyc-1.0.jar
7193 jdk.jcmd/sun.tools.jps.Jps
2058 /usr/share/jenkins/jenkins.war
11933 /var/lib/jenkins/workspace/admin-portal/target/portal-1.0.jar
[geekfkare@localhost ~]#

正如我们在这里看到的,我们得到了所有正在运行的 java 进程的列表。它分别在第一列和第二列中包含正在运行的 java 进程的本地 VM id 和应用程序的名称。现在,要生成线程转储,我们使用带有-l标志的jStack程序,它会创建一个长列表的转储输出。我们还可以将输出通过管道传输到我们选择的某个文本文件。

jstack -l 26680

[geekfkare@localhost ~]# jstack -l 26680
2020-06-27 06:04:53
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):

"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"logback-8" #2316 daemon prio=5 os_prio=0 tid=0x00007f07e0033000 nid=0x4792 waiting on condition [0x00007f07baff8000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"logback-7" #2315 daemon prio=5 os_prio=0 tid=0x00007f07e0251800 nid=0x4791 waiting on condition [0x00007f07bb0f9000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

2、jvisualvm

Jvisualvm是一个 GUI 工具,可帮助我们对 Java 应用程序进行故障排除、监控和分析。它还带有 JVM,可以从我们的 java 安装的/bin目录启动。它非常直观且易于使用。除其他选项外,它还允许我们捕获特定进程的线程转储。

要查看特定进程的线程转储,我们可以右键单击程序并从上下文菜单中选择线程转储。

svg%3E

3、 jcmd

JCMD是 JDK 附带的一个命令行实用程序,用于向 JVM 发送诊断命令请求。

但是,它仅适用于运行 Java 应用程序的本地计算机。它可用于控制 Java 飞行记录、诊断和排除 JVM 和 Java 应用程序的故障。我们可以使用Thread.printjcmd 命令获取 PID 指定的特定进程的线程转储列表。

下面是我们如何使用的示例jcmd

jcmd 28036 Thread.print

C:\Program Files\Java\jdk1.8.0_171\bin>jcmd 28036 Thread.print
28036:
2020-06-27 21:20:02
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):

"Bundle File Closer" #14 daemon prio=5 os_prio=0 tid=0x0000000021d1c000 nid=0x1d4c in Object.wait() [0x00000000244ef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Unknown Source)
        at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.getNextEvent(EventManager.java:403)
        - locked <0x000000076f380a88> (a org.eclipse.osgi.framework.eventmgr.EventManager$EventThread)
        at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:339)

"Active Thread: Equinox Container: 0b6cc851-96cd-46de-a92b-253c7f7671b9" #12 prio=5 os_prio=0 tid=0x0000000022e61800 nid=0xbff4 waiting on condition [0x00000000243ee000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076f388188> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000021a7b000 nid=0x2184 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

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

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

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

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

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

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

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001faf3000 nid=0x36c0 in Object.wait() [0x0000000021eae000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        - locked <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(Unknown Source)
        at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000005806000 nid=0x13c0 in Object.wait() [0x00000000219af000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference.tryHandlePending(Unknown Source)
        - locked <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)

"main" #1 prio=5 os_prio=0 tid=0x000000000570e800 nid=0xbf8 runnable [0x0000000000fec000]
   java.lang.Thread.State: RUNNABLE
        at java.util.zip.ZipFile.open(Native Method)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at org.eclipse.osgi.framework.util.SecureAction.getZipFile(SecureAction.java:307)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getZipFile(ZipBundleFile.java:136)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.lockOpen(ZipBundleFile.java:83)
        at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getEntry(ZipBundleFile.java:290)
        at org.eclipse.equinox.weaving.hooks.WeavingBundleFile.getEntry(WeavingBundleFile.java:65)
        at org.eclipse.osgi.storage.bundlefile.BundleFileWrapper.getEntry(BundleFileWrapper.java:55)
        at org.eclipse.osgi.storage.BundleInfo$Generation.getRawHeaders(BundleInfo.java:130)
        - locked <0x000000076f85e348> (a java.lang.Object)
        at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:599)
        at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:1)
        at org.eclipse.equinox.weaving.hooks.SupplementerRegistry.addSupplementer(SupplementerRegistry.java:172)
        at org.eclipse.equinox.weaving.hooks.WeavingHook.initialize(WeavingHook.java:138)
        at org.eclipse.equinox.weaving.hooks.WeavingHook.start(WeavingHook.java:208)
        at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startActivator(FrameworkExtensionInstaller.java:261)
        at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startExtensionActivators(FrameworkExtensionInstaller.java:198)
        at org.eclipse.osgi.internal.framework.SystemBundleActivator.start(SystemBundleActivator.java:112)
        at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:815)
        at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:1)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:808)
        at org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:765)
        at org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:1005)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle$EquinoxSystemModule.initWorker(EquinoxBundle.java:190)
        at org.eclipse.osgi.container.SystemModule.init(SystemModule.java:99)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:272)
        at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:257)
        at org.eclipse.osgi.launch.Equinox.init(Equinox.java:171)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.startup(EclipseStarter.java:316)
        at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:251)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:661)
        at org.eclipse.equinox.launcher.Main.basicRun(Main.java:597)
        at org.eclipse.equinox.launcher.Main.run(Main.java:1476)

"VM Thread" os_prio=2 tid=0x000000001fae8800 nid=0x32cc runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000005727800 nid=0x3264 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000005729000 nid=0xbdf4 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000572a800 nid=0xae6c runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000572d000 nid=0x588 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000572f000 nid=0xac0 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000005730800 nid=0x380 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000005733800 nid=0x216c runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000005734800 nid=0xb930 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000021a8d000 nid=0x2dcc waiting on condition

JNI global references: 14


C:\Program Files\Java\jdk1.8.0_171\bin>

4、JMC

JMC 代表Java 任务控制。它是 JDK 附带的一个开源 GUI 工具,用于收集和分析 Java 应用程序数据。

它可以从我们 Java 安装的/bin文件夹中启动。Java 管理员和开发人员使用该工具收集有关 JVM 和应用程序行为的详细低级信息。它可以对 Java Flight Recorder收集的数据进行详细而有效的分析。

在启动jmc时,我们可以看到在本地机器上运行的 java 进程列表。远程连接也是可能的。在特定进程上,我们可以右键单击并选择Start Flight Recording,然后在Threads选项卡中检查线程转储。

svg%3E

5、jconsole

jconsole 是一个 Java 管理扩展工具,用于投诉管理和监控。

它还有一组用户可以在 JMX 代理上执行的预定义操作。它使用户能够检测和分析实时程序的堆栈跟踪。它可以从我们 Java 安装的/bin文件夹中启动。

使用jconsole GUI 工具,当我们将每个线程连接到正在运行的 java 进程时,我们可以检查它的堆栈跟踪。然后,在 Thread 选项卡中,我们可以看到所有正在运行的线程的名称。要检测死锁,我们可以单击窗口右下角的检测死锁。如果检测到死锁,它将出现在新选项卡中,否则将显示未检测到死锁 。

svg%3E

6、ThreadMxBean

ThreadMXBean 是java.lang.Management 包下的Java 虚拟机线程系统管理接口。它主要用于检测已进入死锁情况的线程并获取有关它们的详细信息。

我们可以使用 ThreadMxBean 接口以编程方式捕获线程转储。getThreadMXBean()方法ManagementFactory用于获取ThreadMXBean接口的实例。它返回守护程序和非守护程序活动线程的数量。ManagementFactory 是一个工厂类,用于获取 Java 平台的托管 bean。

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

线程dump文件的手动分析

线程转储分析对于查明多线程进程中的问题非常有用。可以通过可视化各个线程转储的状态来解决诸如死锁、锁争用和单个线程转储的 CPU 利用率过高等问题。

通过在分析线程转储后纠正每个线程的状态,可以实现应用程序的最大吞吐量。

例如,假设一个进程正在占用大量 CPU,我们可以找出是否有线程占用 CPU 最多。如果有任何这样的线程,我们将其 LWP 编号转换为十六进制数。然后从线程转储中,我们可以找到 nid 等于之前获得的十六进制数的线程。使用线程的堆栈跟踪,我们可以查明问题。让我们使用以下命令找出线程的进程 ID。

ps -mo pid,lwp,stime,time,cpu -C java

[geekfkare@localhost ~]# ps -mo pid,lwp,stime,time,cpu -C java
       PID        LWP         STIME           TIME              %CPU
26680               -         Dec07          00:02:02           99.5
         -       10039        Dec07          00:00:00           0.1
         -       10040        Dec07          00:00:00           95.5

让我们看看下面的线程转储块。要获取进程 26680 的线程转储,请使用jstack -l 26680

[geekfkare@localhost ~]# jstack -l 26680
2020-06-27 09:01:29
<strong>Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):</strong>

"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=0 tid=0x00007f0858140800 nid=0x683f runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f0858021000 nid=0x683b runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f0858022800 nid=0x683c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f0858024800 nid=0x683d runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f0858026000 nid=0x683e runnable

"VM Periodic Task Thread" os_prio=0 tid=0x00007f08581a0000 nid=0x6847 waiting on condition

JNI global references: 1553

现在,让我们看看我们可以使用线程转储探索哪些内容。如果我们观察线程转储,我们可以看到很多内容,这可能是压倒性的。但是,如果我们一次迈出一步,理解起来可能相当简单。让我们理解第一行

2020-06-27 09:01:29
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):

上面显示了生成转储的时间,以及有关使用的 JVM 的信息。接下来,最后,我们可以看到线程列表,其中第一个是我们的ReferenceHandler线程。

分析阻塞的线程

如果我们分析下面的线程转储日志,我们可以发现它检测到了处于BLOCKED状态的线程,这使得应用程序的性能非常缓慢。因此,如果我们能找到BLOCKED线程,我们可以尝试提取与线程试图获取的锁相关的线程。从当前持有锁的线程分析堆栈跟踪有助于解决问题。

[geekfkare@localhost ~]# jstack -l 26680
.
.
.
.
" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State: <strong>BLOCKED</strong> (on object monitor)
                at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
"DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020]
java.lang.Thread.State: <strong>BLOCKED</strong> (on object monitor)
                at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
.
.
.
.

分析死锁线程

线程转储的另一个非常常用的应用是检测死锁。如果我们分析线程转储,死锁的检测和解决会容易得多。

死锁是涉及至少两个线程的情况,其中一个线程继续执行所需的资源被另一个线程锁定,同时第二个线程所需的资源被第一个线程锁定。

因此,没有一个线程可以继续执行,这会导致死锁情况并最终导致应用程序卡住。如果存在dreadlocks,则线程转储的最后部分将打印出有关死锁的信息,如下所示。

"Thread-0":
waiting to lock monitor 0x00000250e4982480 (object 0x00000000894465b0, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00000250e4982380 (object 0x00000000894465a0, a java.lang.Object),
which is held by "Thread-0"
.
.
.
"Thread-0":
at DeadlockedProgram$DeadlockedRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465b0> (a java.lang.Object)
- locked <0x00000000894465a0> (a java.lang.Object)
at java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
"Thread-1":
at DeadlockedProgram $DeadlockRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465a0> (a java.lang.Object)
- locked <0x00000000894465b0> (a java.lang.Object)
at java.lang.Thread.run(java.base@10.0.1/Thread.java:844)

在这里,我们可以以相当易读的格式看到死锁信息。

除此之外,如果我们将上述所有线程转储块汇总在一起,则它会说明以下信息。

  • 引用处理程序是线程的人类可读名称。
  • #2是线程的唯一 ID。
  • daemon表示线程是否是守护线程。
  • 线程的数字优先级由prio =10 给出。
  • 线程的当前状态由等待条件表示。
  • 然后我们看到堆栈跟踪,其中包括锁定信息。

线程转储分析工具

除了手动分析之外,还有许多工具可用于在线和离线分析线程转储。以下是列出的一些工具,我们可以根据需要使用它们。

首先,让我们探索在线工具。

#1、fastthread

Fast ThreadDevOps工程师最喜欢的用于解决复杂生产问题的线程转储分析工具。这是一个在线Java线程转储分析器,我们可以将线程转储作为文件上传,也可以直接复制粘贴线程转储。

根据大小,它将分析线程转储并显示信息,如屏幕截图所示。

svg%3E

特征

  • 排除 JVM 崩溃、减速、内存泄漏、冻结、CPU 峰值故障
  • 即时 RCA(不要等待供应商)
  • 直观的仪表板
  • REST API 支持
  • 机器学习

#2、Spotify

Spotify 线程转储分析器在Apache 许可证 2.0 版下获得许可。它是一个在线工具,接受线程转储作为文件,或者我们可以直接复制和粘贴线程转储。根据大小,它将分析线程转储并显示信息,如屏幕截图所示。

svg%3E

#3、Jstack review

Jstack.review从浏览器中分析 java 线程转储。此页面仅为客户端。

svg%3E

#4、Site 24×7

工具是检测降低 Java 虚拟机 (JVM) 性能的故障线程的先决条件。可以通过可视化各个线程转储的状态来解决诸如死锁、锁争用和单个线程转储的 CPU 利用率过高等问题。

通过纠正工具提供的每个线程的状态,可以实现应用程序的最大吞吐量。

svg%3E

现在,让我们探索离线工具。

在分析方面,只有最好的工具才足够好。

#1、JProfiler

JProfiler是Java 开发人员中最流行的线程转储分析器之一。JProfiler 的直观 UI 可帮助您解决性能瓶颈、确定内存泄漏并了解线程问题。

JProfiler

JProfiler 支持在以下平台上进行分析:

  • 视窗
  • 苹果系统
  • Linux
  • 自由BSD
  • 索拉里斯
  • 艾克斯
  • 惠普-UX

以下是使 JProfiler 成为在 JVM 上分析我们的应用程序的首选的一些功能。

特征

  • 支持 JDBC、JPA 和 NoSQL 的数据库分析
  • 还提供对 Java 企业版的支持
  • 提供有关 RMI 调用的高级信息
  • 内存泄漏的恒星分析
  • 广泛的质量保证能力
  • 集成的线程分析器与 CPU 分析视图紧密集成。
  • 支持平台、IDE 和应用程序服务器。

#2、IBM TMDA

IBM Thread and Monitor Dump Analyzer for Java ( TMDA ) 是一种工具,它允许识别 Java 线程转储中的挂起、死锁、资源争用和瓶颈。它是 IBM 产品,但提供的 TMDA 工具没有任何保证或支持;但是,随着时间的推移,他们会尝试修复和增强该工具。

svg%3E

#3、ManageEngine

ManageEngine应用程序管理器可以帮助监控 JVM 堆和非堆内存。我们甚至可以配置阈值并通过电子邮件、SMS 等收到警报,并确保 Java 应用程序得到很好的调整。

svg%3E

#4、yourkit

YourKit由以下称为套件的产品组成。

  • Java Profiler – 适用于 Java EE 和 Java SE 平台的全功能低开销分析器。
  • YouMonitor – Jenkins、TeamCity、Gradle、Maven、Ant、JUnit 和 TestNG 的性能监控和分析。
  • .NET Profiler – 易于使用的 .NET 框架性能和内存分析器。

结论

现在您知道了,线程转储如何有助于理解和诊断多线程应用程序中的问题。有了正确的知识,关于线程转储——它们的结构、其中包含的信息等等——我们可以利用它们快速找出问题的原因。

点赞收藏
金色梦想

终身学习。

请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【译】如何用Chronicle 的开源Pauser,平衡低延迟和CPU使用率?

【译】如何用Chronicle 的开源Pauser,平衡低延迟和CPU使用率?

【译】在Java中创建了100万个线程以后

【译】在Java中创建了100万个线程以后

线程池中线程抛了异常,该如何处理?

线程池中线程抛了异常,该如何处理?

5
1