拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 如何分析Java线程Dumps

如何分析Java线程Dumps

白鹭 - 2021-11-24 815 0 0

1.简介

应用程序有时会挂起或运行缓慢,并且找出根本原因并不总是一件容易的事。线程Dump提供了一个运行Java程序的当前状态的快照**。但是,生成的数据包括多个长文件。因此,我们需要分析Java thread dump,并在大量不相关的信息中挖掘问题。

在本教程中,我们将看到如何过滤数据以有效诊断性能问题。此外,我们还将学习检测瓶颈甚至简单的错误。

2. JVM中的线程

JVM使用线程来执行每个内部和外部操作。众所周知,垃圾回收进程具有自己的线程,而且Java应用程序内部的任务也创建自己的线程。

在其生命周期中,线程会经历各种状态。每个线程都有一个跟踪当前操作的执行堆栈。此外,JVM还存储成功调用的所有先前方法。因此,可以分析整个堆栈以研究出现问题时应用程序发生了什么。

为了展示本教程的主题,我们以一个简单的Sender-Receiver应用程序( NetworkDriver )为例。 Java程序发送和接收数据包,因此我们将能够分析幕后发生的事情。

2.1。捕获Java线程Dump

应用程序运行后,可以通过多种方式生成用于诊断的Java线程转储。在本教程中,我们将使用JDK7 +安装中包含的两个实用程序。首先,我们将执行JVM Process Status(jps)命令来发现我们应用程序的PID进程:

$ jps

 80661 NetworkDriver

 33751 Launcher

 80665 Jps

 80664 Launcher

 57113 Application

其次,我们获得应用程序的PID,在本例中为NetworkDriver.旁边的PID NetworkDriver.然后,我们将使用jstack捕获线程转储。最后,我们将结果存储在文本文件中:

$ jstack -l 80661 > sender-receiver-thread-dump.txt

2.2。样本转储的结构

让我们看一下生成的线程转储。第一行显示时间戳,第二行显示有关JVM的信息:

2021-01-04 12:59:29

 Full thread dump OpenJDK 64-Bit Server VM (15.0.1+9-18 mixed mode, sharing):

下一部分显示了安全内存回收(SMR)和非JVM内部线程:

Threads class SMR info:

 _java_thread_list=0x00007fd7a7a12cd0, length=13, elements={

 0x00007fd7aa808200, 0x00007fd7a7012c00, 0x00007fd7aa809800, 0x00007fd7a6009200,

 0x00007fd7ac008200, 0x00007fd7a6830c00, 0x00007fd7ab00a400, 0x00007fd7aa847800,

 0x00007fd7a6896200, 0x00007fd7a60c6800, 0x00007fd7a8858c00, 0x00007fd7ad054c00,

 0x00007fd7a7018800

 }

然后,转储显示线程列表。每个线程包含以下信息:

  • 名称:如果开发人员包含有意义的线程名称,它可以提供有用的信息
  • 优先级(prior):线程的优先级
  • Java ID (tid):JVM给定的唯一ID
  • 本机ID (nid):操作系统提供的唯一ID,可用于提取与CPU或内存处理的相关性
  • 状态State:线程的实际状态
  • 堆栈跟踪:最重要的信息源,可用来解释我们的应用程序正在发生的情况

我们可以从上到下看到快照时不同线程在做什么。让我们仅关注等待消息使用的堆栈中有趣的部分:

"Monitor Ctrl-Break" #12 daemon prio=5 os_prio=31 cpu=17.42ms elapsed=11.42s tid=0x00007fd7a6896200 nid=0x6603 runnable [0x000070000dcc5000]

 java.lang.Thread.State: RUNNABLE


 at sun.nio.ch.SocketDispatcher.read0([email protected]/Native Method)


 at sun.nio.ch.SocketDispatcher.read([email protected]/SocketDispatcher.java:47)


 at sun.nio.ch.NioSocketImpl.tryRead([email protected]/NioSocketImpl.java:261)


 at sun.nio.ch.NioSocketImpl.implRead([email protected]/NioSocketImpl.java:312)


 at sun.nio.ch.NioSocketImpl.read([email protected]/NioSocketImpl.java:350)


 at sun.nio.ch.NioSocketImpl$1.read([email protected]/NioSocketImpl.java:803)


 at java.net.Socket$SocketInputStream.read([email protected]/Socket.java:981)


 at sun.nio.cs.StreamDecoder.readBytes([email protected]/StreamDecoder.java:297)


 at sun.nio.cs.StreamDecoder.implRead([email protected]/StreamDecoder.java:339)


 at sun.nio.cs.StreamDecoder.read([email protected]/StreamDecoder.java:188)


 - locked <0x000000070fc949b0> (a java.io.InputStreamReader)


 at java.io.InputStreamReader.read([email protected]/InputStreamReader.java:181)


 at java.io.BufferedReader.fill([email protected]/BufferedReader.java:161)


 at java.io.BufferedReader.readLine([email protected]/BufferedReader.java:326)


 - locked <0x000000070fc949b0> (a java.io.InputStreamReader)


 at java.io.BufferedReader.readLine([email protected]/BufferedReader.java:392)


 at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)



 Locked ownable synchronizers:


 - <0x000000070fc8a668> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

乍一看,我们看到主堆栈跟踪正在执行java.io.BufferedReader.readLine ,这是预期的行为。如果进一步往下看,我们将在后台看到应用程序执行的所有JVM方法。因此,我们可以通过查看源代码或其他内部JVM处理来确定问题的根源。

在dump结束位置,我们会注意到还有一些其他线程**在执行后台操作,例如垃圾回收(GC)或对象**终止

"VM Thread" os_prio=31 cpu=1.85ms elapsed=11.50s tid=0x00007fd7a7a0c170 nid=0x3603 runnable

 "GC Thread#0" os_prio=31 cpu=0.21ms elapsed=11.51s tid=0x00007fd7a5d12990 nid=0x4d03 runnable

 "G1 Main Marker" os_prio=31 cpu=0.06ms elapsed=11.51s tid=0x00007fd7a7a04a90 nid=0x3103 runnable

 "G1 Conc#0" os_prio=31 cpu=0.05ms elapsed=11.51s tid=0x00007fd7a5c10040 nid=0x3303 runnable

 "G1 Refine#0" os_prio=31 cpu=0.06ms elapsed=11.50s tid=0x00007fd7a5c2d080 nid=0x3403 runnable

 "G1 Young RemSet Sampling" os_prio=31 cpu=1.23ms elapsed=11.50s tid=0x00007fd7a9804220 nid=0x4603 runnable

 "VM Periodic Task Thread" os_prio=31 cpu=5.82ms elapsed=11.42s tid=0x00007fd7a5c35fd0 nid=0x9903 waiting on condition

最后,dump显示Java本机接口(JNI)引用。当发生内存泄漏时,我们应该特别注意这一点,因为它们不会自动垃圾回收:

JNI global refs: 15, weak refs: 0

所有的线程dump的结构非常相似,但是我们希望摆脱为用例生成的非重要数据。另一方面,我们需要对堆栈跟踪产生的大量日志中的重要信息进行保存和分组。让我们来看看如何做!

3.分析线程转储的建议

为了了解我们的应用程序正在发生什么,我们需要有效地分析生成的快照。转储时,我们将获得很多信息**和所有线程的精确数据**。但是,我们需要整理日志文件,进行一些过滤和分组,以从堆栈跟踪中提取有用的提示。准备好转储后,我们将能够使用其他工具来分析问题。让我们看看如何解密样本转储的内容。

3.1。同步问题

过滤掉堆栈跟踪的一个有趣的技巧是线程的状态。我们将主要关注**RUNNABLE或BLOCKED线程,最后是TIMED_WAITING线程**。这些状态将使我们指向两个或多个线程之间发生冲突的方向:

  • 死锁**情况下,多个正在运行的线程在共享对像上保留一个同步块**
  • 线程争用中当某个**线程被阻塞以等待其他线程完成时。**例如,上一节中生成的转储

3.2。执行问题

根据经验,对于异常高的CPU使用率,我们只需要查看RUNNABLE线程即可。我们将线程转储与其他命令一起使用以获取更多信息。这些命令之一是top -H -p PID,它显示特定进程中哪些线程正在消耗OS资源。为了以防万一,我们还需要查看内部JVM线程(例如GC)。另一方面,当处理性能异常低时我们将看一下BLOCKED线程。

在那种情况下,一个转储肯定会不足以了解正在发生的事情。为了比较不同时间的相同线程的堆栈,我们需要**在很**短**的间隔内进行大量转储**。一方面,一张快照并不总是足以找出问题的根源。另一方面,我们需要避免快照之间的干扰(信息过多)。

为了了解线程随时间的演变,建议的最佳实践是**至少进行3次dumps,每10秒进行一次**。另一个有用的技巧是将转储分成小块,以避免崩溃加载文件。

3.3。推荐建议

为了有效地理解问题的根源,我们需要在堆栈跟踪中组织大量信息。因此,我们将考虑以下建议:

  • 在执行问题中,以10秒的间隔捕获几个快照将有助于重点解决实际问题。还建议根据需要拆分文件,以避免加载崩溃
  • 创建新线程时使用命名以更好地识别源代码
  • 根据问题,忽略内部JVM处理(例如GC)
  • 发出异常的CPU或内存使用情况时,请关注**长时间运行或阻塞的线程**
  • 通过使用top -H -p PID将线程的堆栈与CPU处理相关联
  • 最重要的是,使用分析器工具

手动分析Java线程转储可能是一件乏味的工作。对于简单的应用程序,可以识别产生问题的线程。另一方面,对于复杂的情况,我们需要工具来简化此任务。在下一部分中,我们将使用为示例线程争用生成的转储,展示如何使用这些工具。

4.在线工具

有几种在线工具可用。使用此类软件时,我们需要考虑安全性问题。请记住,我们可能会与第三方实体共享日志

4.1。快速线程

FastThread可能是分析生产环境中的线程转储的最佳联机工具。它提供了一个非常漂亮的图形用户界面。它还包括多种功能,例如线程的CPU使用率,堆栈长度以及最常用和最复杂的方法:

如何分析Java线程转储

FastThread集成了REST API功能以自动分析线程转储。使用简单的cURL命令,可以立即发送结果。主要缺点是安全性,因为它**会将堆栈跟踪存储在云中**。

4.2。 JStack评论

JStack Review是一个在线工具,可以分析浏览器中的转储。它仅在客户端,因此没有数据存储在计算机外部。从安全角度来看,这是使用它的主要优势。它提供了所有线程的图形概述,显示了正在运行的方法,还按状态将它们分组。 JStack Review将产生堆栈的线程与其余线程分开,这对于忽略内部进程非常重要。最后,它还包括同步器和被忽略的行:

如何分析Java线程转储

4.3。 Spotify在线Java线程转储分析器

Spotify在线Java线程转储分析器是一个用JavaScript编写的在线开源工具。它以纯文本格式显示结果,该结果以带和不带堆栈的方式分隔线程。它还显示正在运行的线程中的顶级方法:

如何分析Java线程转储

5.独立应用程序

我们也可以在本地使用几个独立的应用程序。

5.1。 JProfiler

JProfiler是市场上最强大的工具,在Java开发人员社区中广为人知。可以使用10天的试用许可证来测试功能。 JProfiler允许创建概要文件并将正在运行的应用程序附加到它们。它包括多种功能,可以在现场识别问题,例如CPU和内存使用情况以及数据库分析。它还支持与IDE的集成:

如何分析Java线程转储

5.2。 IBM Java线程监视器和转储分析器(TMDA)

IBM TMDA可用于识别线程争用,死锁和瓶颈。它是免费分发和维护的,但不提供IBM的任何保证或支持:

如何分析Java线程转储

5.3。 Irockel**线程转储分析器(TDA)**

Irockel TDA是LGPL v2.1许可的独立开源工具。最新版本(v2.4)于2020年8月发布,因此维护良好。它将线程转储显示为树,还提供了一些统计信息以简化导航:

如何分析Java线程转储

最后,IDE支持线程转储的基本分析,因此可以在开发期间调试应用程序。

5.结论

在本文中,我们演示了Java线程转储分析如何帮助我们查明同步或执行问题。

最重要的是,我们回顾了如何正确分析它们,包括对组织快照中嵌入的大量信息的建议。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *