让我们谈谈线程转储,以及如何分析它。
我们还将讨论它如何帮助查明问题以及您可以使用的一些分析器。
目录
什么是线程?
进程是加载到计算机内存中并正在执行的计算机程序。 它可以由一个处理器或一组处理器执行。 进程在内存中用重要信息描述,例如变量存储、文件句柄、程序计数器、寄存器和信号等。
一个进程可以由许多称为线程的轻量级进程组成。 这有助于实现并行性,其中一个进程被分成多个线程。 这会带来更好的性能。 一个进程中的所有线程共享相同的内存空间并且相互依赖。
线程转储
当进程正在执行时,我们可以使用线程转储检测进程中线程的当前执行状态。 线程转储包含程序执行期间在特定时间点处于活动状态的所有线程的快照。 它包含有关线程及其当前状态的所有相关信息。
今天的现代应用程序涉及多个线程。 每个线程都需要一定的资源,执行与进程相关的某些活动。 这可以提高应用程序的性能,因为线程可以利用可用的 CPU 内核。
但是也有折衷,例如,有时多个线程可能无法很好地相互协调,并且可能会出现死锁情况。 因此,如果出现问题,我们可以使用线程转储来检查线程的状态。
Java 中的线程转储
JVM 线程转储是在特定时间点作为进程一部分的所有线程的状态列表。 它包含有关线程堆栈的信息,以堆栈跟踪的形式呈现。 因为是明文写的,内容可以保存起来,方便以后查看。 线程转储的分析可以帮助
- 优化 JVM 性能
- 优化应用程序性能
- 诊断问题,例如死锁、线程争用等。
线程转储的生成
有很多方法可以生成线程转储。 以下是一些基于 JVM 的工具,可以从命令行/终端(CLI 工具)或 Java 安装文件夹的 /bin(GUI 工具)目录执行。
让我们探索它们。
#1。 堆栈
生成线程转储的最简单方法是使用 jStack。 jStack 随 JVM 一起提供,可以从命令行使用。 在这里,我们需要要为其生成线程转储的进程的 PID。 要获取 PID,我们可以使用 jps 命令,如下所示。
jps -l
jps 列出所有 java 进程 ID。
在 Windows 上
C:Program FilesJavajdk1.8.0_171bin>jps -l 47172 portal 6120 sun.tools.jps.Jps C:Program FilesJavajdk1.8.0_171bin>
在 Linux 上
[[email protected] ~]# 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 [[email protected] ~]#
正如我们在这里看到的,我们得到了所有正在运行的 java 进程的列表。 它包含正在运行的 java 进程的本地 VM id 和分别在第一列和第二列中的应用程序的名称。 现在,为了生成线程转储,我们使用带有 –l 标志的 jStack 程序,它创建一个长列表的转储输出。 我们还可以将输出通过管道传输到我们选择的某个文本文件。
jstack -l 26680
[[email protected] ~]# 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 目录启动。 它非常直观且易于使用。 在其他选项中,它还允许我们捕获特定进程的线程转储。
要查看特定进程的线程转储,我们可以右键单击该程序并从上下文菜单中选择线程转储。
#3。 jcmd
JCMD 是 JDK 附带的命令行实用程序,用于向 JVM 发送诊断命令请求。
但是,它仅适用于运行 Java 应用程序的本地计算机。 它可用于控制 Java 飞行记录、诊断和排除 JVM 和 Java 应用程序的故障。 我们可以使用 jcmd 的 Thread.print 命令来获取由 PID 指定的特定进程的线程转储列表。
下面是我们如何使用 jcmd 的示例。
jcmd 28036 线程打印
C:Program FilesJavajdk1.8.0_171bin>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 FilesJavajdk1.8.0_171bin>
#4。 江铃汽车
JMC 代表 Java 任务控制。 它是 JDK 附带的开源 GUI 工具,用于收集和分析 Java 应用程序数据。
它可以从我们的 Java 安装的 /bin 文件夹中启动。 Java 管理员和开发人员使用该工具收集有关 JVM 和应用程序行为的详细低级信息。 它可以对 Java Flight Recorder 收集的数据进行详细、高效的分析。
在启动 jmc 时,我们可以看到在本地计算机上运行的 java 进程列表。 远程连接也是可能的。 在特定进程上,我们可以右键单击并选择 Start Flight Recording,然后在 Threads 选项卡中检查线程转储。
#5。 控制台
jconsole 是一个用于投诉管理和监控的 Java 管理扩展工具。
它还具有一组用户可以在 JMX 代理上执行的预定义操作。 它使用户能够检测和分析实时程序的堆栈跟踪。 它可以从我们的 Java 安装的 /bin 文件夹中启动。
使用 jconsole GUI 工具,我们可以在将线程连接到正在运行的 Java 进程时检查每个线程的堆栈跟踪。 然后,在线程选项卡中,我们可以看到所有正在运行的线程的名称。 要检测死锁,我们可以单击窗口右下角的检测死锁。 如果检测到死锁,它将出现在新选项卡中,否则将显示未检测到死锁。
#6。 ThreadMxBean
ThreadMXBean是Java虚拟机线程系统的管理接口,属于java.lang.Management包。 它主要用于检测进入死锁情况的线程并获取有关它们的详细信息。
我们可以使用 ThreadMxBean 接口以编程方式捕获线程转储。 ManagementFactory 的 getThreadMXBean() 方法用于获取 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 (); }
手动分析线程转储
线程转储分析对于查明多线程进程中的问题非常有用。 通过可视化各个线程转储的状态,可以解决诸如死锁、锁争用和各个线程转储的 CPU 使用率过高等问题。
通过在分析线程转储后纠正每个线程的状态,可以实现应用程序的最大吞吐量。
例如,假设一个进程正在使用大量 CPU,我们可以找出是否有线程使用 CPU 最多。 如果有任何这样的线程,我们将其 LWP 编号转换为十六进制数。 然后从thread dump中,我们可以找到nid等于之前得到的16进制数的thread。 使用线程的堆栈跟踪,我们可以查明问题所在。 让我们使用以下命令找出线程的进程 ID。
ps -mo pid,lwp,stime,time,cpu -C java
[[email protected] ~]# 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
[[email protected] ~]# 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
全线程转储 Java HotSpot(TM) 64 位服务器 VM(25.221-b11 混合模式):
上面显示了生成转储的时间,以及有关使用的 JVM 的信息。 接下来,最后,我们可以看到线程列表,其中第一个是我们的 ReferenceHandler 线程。
分析阻塞的线程
如果我们分析下面的线程转储日志,我们可以发现它检测到具有 BLOCKED 状态的线程,这使得应用程序的性能非常慢。 因此,如果我们能够找到 BLOCKED 线程,我们就可以尝试提取与线程试图获取的锁相关的线程。 从当前持有锁的线程分析堆栈跟踪有助于解决问题。
[[email protected] ~]# 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) . . . .
分析死锁线程
线程转储的另一个非常常用的应用是死锁检测。 如果我们分析线程转储,死锁的检测和解决会容易得多。
死锁是涉及至少两个线程的情况,其中一个线程继续执行所需的资源被另一个线程锁定,同时第二个线程所需的资源被第一个线程锁定。
因此,没有线程可以继续执行,这会导致死锁情况并导致应用程序卡住。 如果存在死锁,则线程转储的最后部分将打印出有关死锁的信息,如下所示。
"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([email protected].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([email protected]/Thread.java:844)
在这里,我们可以以一种相当易于阅读的格式查看死锁信息。
除此之外,如果我们将上述所有线程转储块汇总在一起,则它会显示以下信息。
- 引用处理程序是线程的人类可读名称。
- #2 是线程的唯一标识。
- daemon 表示线程是否是守护线程。
- 线程的数字优先级由 prio=10 给出。
- 线程的当前状态由等待条件表示。
- 然后我们看到堆栈跟踪,其中包括锁定信息。
线程转储分析器
除了手动分析之外,还有许多工具可用于在线和离线分析线程转储。 下面列出了一些工具,我们可以根据需要使用。
首先,让我们探索在线工具。
#1。 快速线程
快速线程 是 DevOps 工程师最喜欢的线程转储分析工具,用于解决复杂的生产问题。 这是一个在线的 Java 线程转储分析器,我们可以将线程转储作为文件上传,也可以直接复制并粘贴线程转储。
根据大小,它将分析线程转储并显示信息,如屏幕截图所示。
特征
- 排除 JVM 崩溃、速度减慢、内存泄漏、冻结、CPU 尖峰问题
- 即时 RCA(不要等待供应商)
- 直观的仪表板
- REST API 支持
- 机器学习
#2。 Spotify 线程转储分析器
这 Spotify 线程转储分析器 根据 Apache 许可证 2.0 版获得许可。 它是一个在线工具,接受线程转储作为文件,或者我们可以直接复制和粘贴线程转储。 根据大小,它将分析线程转储并显示信息,如屏幕截图所示。
#3。 Jstack 评论
Jstack.评论 从浏览器中分析 java 线程转储。 此页面仅为客户端。
#4。 现场 24×7
这个 工具 是检测降低 Java 虚拟机 (JVM) 性能的故障线程的先决条件。 通过可视化各个线程转储的状态,可以解决诸如死锁、锁争用和各个线程转储的 CPU 使用率过高等问题。
通过纠正该工具提供的每个线程的状态,可以实现应用程序的最大吞吐量。
现在,让我们探索离线工具。
在分析方面,只有最好的工具才足够好。
#1。 J探查器
J探查器 是 Java 开发人员中最流行的线程转储分析器之一。 JProfiler 直观的 UI 可帮助您解决性能瓶颈、确定内存泄漏并了解线程问题。
JProfiler 支持在以下平台上进行分析:
- 视窗
- 苹果系统
- Linux
- FreeBSD
- 索拉里斯
- 艾克斯
- 用户体验
以下是一些使 JProfiler 成为在 JVM 上分析我们的应用程序的首选的功能。
特征
- 支持 JDBC、JPA 和 NoSQL 的数据库分析
- 还提供对 Java 企业版的支持
- 提供有关 RMI 调用的高级信息
- 内存泄漏的恒星分析
- 广泛的质量保证能力
- 集成的线程分析器与 CPU 分析视图紧密集成。
- 支持平台、IDE 和应用程序服务器。
#2。 IBM TMDA
IBM Thread and Monitor Dump Analyzer for Java (TMDA) 是一种工具,可以识别 Java 线程转储中的挂起、死锁、资源争用和瓶颈。 它是 IBM 产品,但提供的 TMDA 工具没有任何保证或支持; 但是,随着时间的推移,他们会尝试修复和增强该工具。
#3。 管理引擎
管理引擎 应用程序管理器可以帮助监控 JVM 堆和非堆内存。 我们甚至可以配置阈值并通过电子邮件、SMS 等方式收到警报,并确保 Java 应用程序得到良好调整。
#4。 你的工具包
你的工具包 由以下产品组成,称为套件。
- Java Profiler – 适用于 Java EE 和 Java SE 平台的全功能低开销分析器。
- YouMonitor – Jenkins、TeamCity、Gradle、Maven、Ant、JUnit 和 TestNG 的性能监控和分析。
- .NET Profiler – 易于使用的 .NET 框架性能和内存分析器。
结论
现在您知道了,线程转储对于理解和诊断多线程应用程序中的问题有何帮助。 以适当的 知识,关于线程转储——它们的结构、其中包含的信息等等——我们可以利用它们来快速识别问题的原因。