104

我试图更多地了解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 之后方括号中的数字是什么?

然后对于堆栈跟踪本身:

  • 等待 <.....> (java.lang....)是什么意思, <..>中的数字是多少
  • 这是什么意思锁定<.....>(一个java.lang....)同样的问题,什么是在<..>

我认为锁定这个词在某种程度上与等待条件有关,但是,我错了。事实上,我想知道为什么 locked 重复了 3 次,但线程处于可运行状态,如在同一个转储中看到的:

"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?

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

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

谢谢

4

2 回答 2

118

TID 是tad 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 线程由操作系统的本机线程实现。每个线程都由一条粗体线表示,例如:

“线程 1”(TID:0x9017A0,sys_thread_t:0x23EAC8,状态:R,本机 ID:0x6E4)prio=5

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

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

“等待”似乎是与 jvm 本身关联的守护线程,而不是应用程序线程本身。当你得到一个“in Object.wait()”时,这意味着守护线程,这里的“终结器”,正在等待有关锁定对象的通知,在这种情况下,它会显示它正在等待什么通知:“-等待 <0x27ef0288> (a 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 故障排除指南

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

于 2011-09-29T15:47:29.327 回答
12

除了@James Drinkard 的出色回答:

请注意,根据底层实现,在本机方法中阻塞的线程的java.lang.Thread.StateRUNNABLE可能会报告为,其中A 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.

事实证明,这种描述还包括在诸如轮询或读取操作之类的操作系统调用中被阻塞——大概是因为无法保证 JVM 可以知道本机方法调用何时在操作系统级别阻塞。

我看到的许多关于 JVM 线程转储的讨论要么完全忽略了这种可能性,要么在没有考虑其含义的情况下轻率地略过它——尤其是监控工具可能会混淆地报告几个这样的线程正在“运行”,而且它们都在 100% 运行。

于 2016-04-06T17:47:35.897 回答