如何分析Java线程转储?

106

我想更深入地了解Java,特别是内存管理和线程方面的知识。因此,我最近对查看线程转储感到很有兴趣。

以下是使用Java内置工具VisualVM获取的一个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后的方括号中的数字代表什么?

接下来是关于堆栈跟踪本身的问题:

  • waiting on <.....> (a java.lang....) 是什么意思,以及 <..> 中的数字代表什么?
  • locked <.....> (a java.lang....) 同样的问题,<..> 中的内容是什么?

我曾认为单词“locked”与等待条件有关,但我错了。事实上,我想知道为什么“locked”重复三次,但线程在相同的转储中处于可运行状态:

"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是什么?

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

如果您能回答我所有的问题,我将非常感激。

谢谢

2个回答

119

请访问此链接:Java-level thread ID,了解这两个术语的定义和详细说明。
我在IBM的网站上找到了这个链接:如何解释线程转储。其中更详细地介绍了这一点。
它解释了等待的含义: 锁防止多个实体访问共享资源。Java™中的每个对象都有一个关联的锁(通过使用synchronized块或方法获得)。对于JVM,线程在JVM中竞争各种资源和锁定Java对象。
然后它将监视器描述为一种特殊的锁定机制,在JVM中用于允许线程之间灵活同步。为了本节的目的,请交替阅读监视器和锁定术语。
然后它进一步解释:
为了避免在每个对象上都有一个监视器,JVM通常使用类或方法块中的标志来指示该项已锁定。大多数情况下,代码段将在没有争用的情况下过渡某些已锁定的部分。因此,警卫标志足以保护这个代码片段。这称为平面监视器。但是,如果另一个线程想要访问已锁定的某些代码,则已发生真正的争用。现在,JVM必须创建(或膨胀)监视器对象以容纳第二个线程,并安排用于协调对代码部分的访问的信号机制。这个监视器现在被称为膨胀的监视器。
下面是您从线程转储中看到的行的更详细说明。Java线程由操作系统的本机线程实现。每个线程由粗体行代表,例如:
"Thread-1" (TID:0x9017A0, sys_thread_t:0x23EAC8, state:R, native ID:0x6E4) prio=5
以下6个项目解释了此内容,我已按照示例进行了匹配,括号[]中的值:
1.名称[Thread-1], 2.标识符[0x9017A0], 3.JVM数据结构地址[0x23EAC8], 4.当前状态[R], 5.本机线程标识符[0x6E4], 6.和优先级[5]。
“等待”似乎是与jvm本身而不是应用程序线程相关联的守护程序线程。当您在“Object.wait()”中得到时,这意味着守护程序线程,在这种情况下,“finalizer”,正在等待有关对象锁定的通知,它显示了您正在等待什么通知: “- waiting on <0x27ef0288> (a java.lang.ref.ReferenceQueue$Lock)”
ReferenceQueue的定义是: 引用队列,注册引用对象在适当的可达性更改被检测后由垃圾收集器附加到其中。
终结器线程运行
Current thread (0x0805ac88):  JavaThread "main" [_thread_in_native, id=21139]
                    |             |         |            |          +-- ID
                    |             |         |            +------------- state
                    |             |         +-------------------------- name
                    |             +------------------------------------ type
                    +-------------------------------------------------- pointer
线程指针是指向Java虚拟机内部线程结构的指针。一般情况下,除非您正在调试现有的Java虚拟机或核心文件,否则它不会引起兴趣。
这个描述来自:Java SE 6 with HotSpot VM故障排除指南
以下是关于线程转储的一些链接:

较新的Java版本(9及以上?)在线程描述符行中提供了额外的有用信息,例如cpu=nnnn表示线程实际占用的CPU时间(以秒为单位)。例如,如果你有一个繁忙循环("100%利用率")的线程,你可以查看cpu,倒数计算,并找到它实际进入忙碌循环的大致时间,以便通过带时间戳的日志等进行进一步的故障排除。 - Janaka Bandara

12

除了 @James Drinkard 的精彩回答:

请注意,取决于底层实现,被阻塞在本机方法中的线程的 java.lang.Thread.State 可能会报告为 RUNNABLE,其中 运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源,如处理器。

事实证明,这个描述也包括在 OS 调用(例如 poll 或 read 操作)中被阻塞 - 大概是因为 JVM 无法保证知道本机方法调用已经在 OS 级别上被阻塞。

我看过的许多关于 JVM 线程转储的讨论要么完全忽略了这种可能性,要么轻描淡写地跳过了考虑其影响的过程,其中最重要的是监视工具可能会令人困惑地报告多个该类线程正在 '运行',而且它们都以 100% 运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接