如何分析Java线程转储?

2022-08-31 10:04:32

我试图更多地了解java,特别是关于内存管理和线程。出于这个原因,我最近对查看线程转储感兴趣。

以下是使用 VisualVM(一种用于 java 的内置工具)从 Web 应用程序中获取的几行:

"Finalizer" daemon prio=8 tid=0x02b3d000 nid=0x898 in Object.wait() [0x02d0f000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
    - locked <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

   Locked ownable synchronizers:
    - None

"Reference Handler" daemon prio=10 tid=0x02b3b800 nid=0x494 in Object.wait() [0x02cbf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x27ef0310> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:485)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
    - locked <0x27ef0310> (a java.lang.ref.Reference$Lock)

首先,我对一些变量名称有疑问:

  • tid和nid是什么意思?
  • Object.wait 后面的方形括号中的数字是什么?

然后对于堆栈跟踪本身:

  • 等待<是什么意思.....> (a java.lang....)以及<中的数字是多少。>
  • 锁定<是什么意思.....> (a java.lang....)同样的问题,<是什么。>

我以为锁定这个词在某种程度上与等待条件有关,但是,我错了。实际上,我想知道为什么锁定重复了三次,但线程处于可运行状态,如同一转储所示:

"Thread-0" prio=6 tid=0x02ee3800 nid=0xc1c runnable [0x03eaf000]
   java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:199)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
    - locked <0x23963378> (a java.io.BufferedInputStream)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
    - locked <0x23968450> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:167)
    at java.io.BufferedReader.fill(BufferedReader.java:136)
    at java.io.BufferedReader.readLine(BufferedReader.java:299)
    - locked <0x23968450> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:362)
    at org.codehaus.plexus.util.cli.StreamPumper.run(StreamPumper.java:145)

最后,这是最糟糕的:

"CompilerThread0" daemon prio=10 tid=0x02b81000 nid=0x698 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

此线程处于可运行状态,但它正在等待条件。什么条件,什么是0x00000?

为什么堆栈跟踪如此之短,没有任何线程类的证据?

如果你能回答我的所有问题,我将不胜感激。

谢谢


答案 1

TID 是 thead id,NID 是:Native thread ID。此 ID 高度依赖于平台。它是 jstack 线程转储中的 NID。在 Windows 上,它只是进程中的操作系统级线程 ID。在Linux和Solaris上,它是线程的PID(反过来又是一个轻量级的过程)。在Mac OS X上,它被称为本机pthread_t值。

转到此链接:Java 级线程 ID:以获取这两个术语的定义和进一步解释。

在 IBM 的网站上,我发现了这个链接:如何解释线程转储。更详细地涵盖了这一点:

它解释了等待意味着什么:锁可以防止多个实体访问共享资源。Java™ 中的每个对象都有一个关联的锁(通过使用同步块或方法获得)。在 JVM 的情况下,线程争用 JVM 中的各种资源,并锁定 Java 对象。

然后,它将监视器描述为一种特殊的锁定机制,该机制在 JVM 中使用,以允许线程之间的灵活同步。出于本节的目的,请阅读术语“监视”和“可互换锁定”。

然后它更进一步:

为了避免在每个对象上都有一个监视器,JVM 通常在类或方法块中使用标志来指示项目已锁定。大多数情况下,一段代码将传输一些锁定的部分而不会发生争用。因此,守护标志足以保护这段代码。这称为平面监视器。但是,如果另一个线程想要访问某些被锁定的代码,则发生了真正的争用。JVM 现在必须创建(或膨胀)监视器对象以容纳第二个线程,并安排信令机制来协调对代码段的访问。此监视器现在称为充气监视器。

下面更详细地解释了您在线程转储行中看到的内容。Java 线程由操作系统的本机线程实现。每个线程由粗体行表示,例如:

“Thread-1” (TID:0x9017A0, sys_thread_t:0x23EAC8, state:R, native ID:0x6E4) prio=5

*以下6项解释了这一点,因为我从示例中匹配了它们,括号中的值[]:

  1. 名称 [线程-1],
  2. 标识符 [0x9017A0],
  3. JVM 数据结构地址 [0x23EAC8],
  4. 当前状态 [R],
  5. 本机线程标识符 [0x6E4],
  6. 和优先级 [5]。

“wait on”似乎是与 jvm 本身关联的守护进程线程,而不是应用程序线程 perse。当你得到一个“in Object.wait()”时,这意味着守护进程线程,这里的“终结器”,正在等待有关对象锁定的通知,在这种情况下,它会告诉你它正在等待什么通知:“- 等待<0x27ef0288>(java.lang.ref.ReferenceQueue$Lock)”

ReferenceQueue 的定义是:引用队列,在检测到适当的可访问性更改后,垃圾回收器会将已注册的引用对象追加到该队列中。

终结器线程运行,以便垃圾回收运行以清理与对象关联的资源。如果我直接看到它,终结器无法将锁锁定到此对象:java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118),因为java对象正在运行一个方法,因此终结器线程被锁定,直到该对象完成其当前任务。

此外,终结器不仅希望回收内存,而且比清理资源更复杂。我需要对它做更多的研究,但是如果你有打开的文件,套接字等...与对象方法相关,则终结器也将释放这些项目。

在线程转储中 Object.wait 后面的方形括号中的数字是什么?

它是内存中指向线程的指针。以下是更详细的描述:

C.4.1 线程信息

线程部分的第一部分显示引发致命错误的线程,如下所示:

Current thread (0x0805ac88):  JavaThread "main" [_thread_in_native, id=21139]
                    |             |         |            |          +-- ID
                    |             |         |            +------------- state
                    |             |         +-------------------------- name
                    |             +------------------------------------ type
                    +-------------------------------------------------- pointer

线程指针是指向 Java VM 内部线程结构的指针。除非您正在调试实时 Java VM 或核心文件,否则它通常无关紧要。

最后一个描述来自:带有 HotSpot VM 的 Java SE 6 故障排除指南

以下是有关线程转储的更多链接:


答案 2

此外,@James Drinkard的精彩回答:

请注意,根据底层实现的不同,在本机方法中被阻止的线程的 java.lang.Thread.State 可能会报告为 ,其中RUNNABLEA thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

事实证明,这种描述还包括在OS调用(如轮询或读取操作)中被阻止 - 大概是因为不能保证JVM可以知道本机方法调用何时在操作系统级别被阻止。

我看到的许多关于JVM线程转储的讨论要么完全忽略这种可能性,要么轻率地浏览它而不考虑其影响 - 其中最重要的是,监控工具可能会令人困惑地报告几个这样的线程正在“运行”,而且它们都以100%的速度运行。