如何找出哪个线程持有监视器?

How to find out which thread holds the monitor?

我的应用程序使用 Gson 2.2POJOs 转换为 JSON。当我进行负载测试时,我偶然发现 Gson 构造函数中阻塞了很多线程:

"http-apr-28201-exec-28" #370 daemon prio=5 os_prio=0 tid=0x0000000001ee7800 nid=0x62cb waiting for monitor entry [0x00007fe64df9a000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at com.google.gson.Gson.<init>(Gson.java:200)
    at com.google.gson.Gson.<init>(Gson.java:179)

线程转储不显示任何线程持有 [0x00007fe64df9a000] monitor我怎样才能知道是谁持有它?

Gson 代码 at line 200 看起来很无辜:

// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);

我在 Linux

上使用 JRE 1.8.0_91

tl;dr 我认为您 运行 陷入与 GC 相关的行为,其中线程被置于等待状态以允许垃圾收集。


我没有全部的真相,但我希望提供一些见解。

首先 要意识到括号中的数字 [0x00007fe64df9a000] 不是监视器的地址。可以看到转储中所有线程的括号中的数字,甚至是处于 运行 状态的线程。数量也没有变化。来自我的测试转储的示例:

main" #1 prio=5 os_prio=0 tid=0x00007fe27c009000 nid=0x27e5c runnable [0x00007fe283bc2000]
   java.lang.Thread.State: RUNNABLE
        at Foo.main(Foo.java:12)

我不确定这个数字是什么意思,但 this page 暗示它是:

... the pointer to the Java VM internal thread structure. It is generally of no interest unless you are debugging a live Java VM or core file.

虽然解释的跟踪格式有点不同,所以我不确定我是否正确。

显示实际监视器地址时转储的外观:

"qtp48612937-70" #70 prio=5 os_prio=0 tid=0x00007fbb845b4800 nid=0x133c waiting for monitor entry [0x00007fbad69e8000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:233)
        - waiting to lock <0x00000005b8d68e90> (a java.lang.Object)

注意跟踪中的 waiting to lock 行,监视器的地址与括号中的数字不同。

我们看不到所涉及的监视器的地址这一事实表明该监视器仅存在于本机代码中。

其次,涉及到的Gson代码根本不包含任何同步。该代码只是将一个元素添加到 ArrayList (假设没有进行任何字节码操作,并且没有在低级别进行任何可疑操作)。即,在此调用中看到线程等待标准同步监视器是没有意义的。

我发现 some, indications 当有很多 GC 正在进行时,线程可以显示为等待监视器条目。

我写了一个简单的测试程序来尝试通过向数组列表添加很多元素来重现它:

List<String> l = new ArrayList<>();
while (true) {
    for (int i = 0; i < 100_100; i++) {
            l.add("" + i);
    }
    l = new ArrayList<>();
}

然后我获取了这个程序的线程转储。偶尔我 运行 进入以下痕迹:

"main" #1 prio=5 os_prio=0 tid=0x00007f35a8009000 nid=0x12448 waiting on condition [0x00007f35ac335000]
   java.lang.Thread.State: RUNNABLE
      at Foo.main(Foo.java:10)   <--- Line of l.add()

虽然与 OP 的跟踪不同,但在不涉及同步时有一个线程 waiting on condition 很有趣。我用小heap体验的比较频繁,说明可能是GC相关。

另一种可能是包含同步的代码已被 JIT 编译,这使您无法看到监视器的实际地址。但是,我认为这不太可能,因为您在 ArrayList.add 上体验过它。如果是这样的话,我不知道有没有办法找出显示器的实际持有者。

如果您没有 GC 问题,那么实际上可能有一些线程已获取对象上的锁,而卡住的线程正在等待获取同一对象上的锁。找出方法是寻找

- waiting to lock <some_hex_address> (a <java_class>)

例子是

- waiting to lock <0x00000000f139bb98> (a java.util.concurrent.ConcurrentHashMap)

在条目的线程转储中显示 waiting for monitor entry。找到它后,您可以搜索已经获取地址为 <some_hex_address> 的对象上的锁的线程,对于示例 -

,它看起来像这样
- locked <0x00000000f139bb98> (a java.util.concurrent.ConcurrentHashMap)

现在您可以查看该线程的堆栈跟踪,以确定是哪一行代码获取了它。