如何找出哪个线程容纳显示器?

2022-09-01 11:04:38

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

"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第 200 行的代码看起来很无辜:

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

我正在使用JRE 1.8.0_91Linux


答案 1

tl;博士我认为您遇到了与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)

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

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

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

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

"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

我发现了一些迹象,表明当有很多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的跟踪不同,但在不涉及同步时拥有线程很有趣。我更频繁地使用较小的堆来体验它,这表明它可能与GC相关。waiting on condition

另一种可能性是包含同步的代码已经过 JIT 编译,从而阻止您查看监视器的实际地址。但是,我认为这不太可能,因为您在.如果是这样的话,我知道没有办法找出显示器的实际持有者。ArrayList.add


答案 2

如果您没有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)

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