Thread.State 之间有什么区别:等待(停车)与在 sun.misc.Unsafe.park 处阻塞()
What is the difference between Thread.State: WAITING (parking) vs BLOCKED at sun.misc.Unsafe.park()
我有一个连接到 Hazelcast 的应用程序。最近我发现对 hazelcast 的请求最终开始变得无响应,因此,我对 Hazelcast 进程进行了线程转储。在分析来自开发和生产环境的线程转储时,我发现池中等待任务的线程在不同的环境中处于不同的状态。
在生产服务器上,线程被阻塞(500 个中有 337 个)。
在开发环境中,没有线程被阻塞(50% 作为 runnable 和 50% 作为 waiting 60 个线程)。
那些阻塞线程是否正在等待某些线程无限期持有的同步块? 500 个线程是否过多(我收到一些分析器的警告)?这会导致我的应用程序无响应吗?
导致此状态的可能原因是什么以及如何解决?
线程转储(生产):
Thread 120713: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.ForkJoinPool.awaitWork(java.util.concurrent.ForkJoinPool$WorkQueue, int) @bci=350, line=1824 (Compiled frame)
- java.util.concurrent.ForkJoinPool.runWorker(java.util.concurrent.ForkJoinPool$WorkQueue) @bci=44, line=1693 (Interpreted frame)
- java.util.concurrent.ForkJoinWorkerThread.run() @bci=24, line=157 (Interpreted frame)
Thread 120743: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=175 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=2039 (Compiled frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=442 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=149, line=1074 (Compiled frame)
Thread 120743: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.park() @bci=5, line=304 (Compiled frame)
- com.hazelcast.internal.util.concurrent.MPSCQueue.takeAll() @bci=83, line=231 (Compiled frame)
- com.hazelcast.internal.util.concurrent.MPSCQueue.take() @bci=12, line=153 (Compiled frame)
- com.hazelcast.client.spi.impl.ClientResponseHandlerSupplier$ResponseThread.doRun() @bci=17, line=164 (Compiled
Thread 128753: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=20, line=215 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) @bci=78, line=2078 (Compiled frame)
- java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=124, line=1093 (Compiled frame)
- java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=1, line=809 (Compiled frame)
来自开发环境的线程转储:
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c1a1bc38> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
Thread states - 这里是对线程状态的一些解释。
NEW
The thread has not yet started.
RUNNABLE
The thread is executing in the JVM.
BLOCKED
The thread is blocked waiting for a monitor lock.
WAITING
The thread is waiting indefinitely for another thread to perform a particular action.
TIMED_WAITING
The thread is waiting for another thread to perform an action for up to a specified waiting time.
TERMINATED
The thread has exited.
BLOCKED 状态应该关注它是否对相同的线程存在很长时间。
这当然取决于你的情况 - 你如何处理数据,你如何创建线程(和线程池),你的关键部分是什么以及它们如何相互作用。
产品的单线程转储是不够的 - 你应该采取几个转储和
- 比较发生的事情和
- 线程有多长 running/waiting
- 这是发生在高负载时还是高负载之后
- 你的线程数是否会随着时间的推移而增加,等等
因此无法判断在这个特定时间点 500 个阻塞的线程是好是坏,但可以肯定的是它是令人担忧的。一个线程大约需要 2MB 来初始化和分配内存,所以它是 1GB 内存。
很可能某些线程占用了一些关键部分,导致您的问题和应用程序无响应。您可能会使用阻塞方法等从队列中读取一些非常复杂的情况。
可能的行动方案:
- 进行几次转储并进行比较 - 发生了什么变化?哪些线程仍被阻塞?
- 检查您是否可以在转储的堆栈跟踪中查明阻塞线程中的调用(在您的包前缀或 java's/hazelcast 的包中)。
- 使用跟踪工具 (flight-recorder / jvisualvm) 检查线程的增长以及线程(被阻塞)的创建时间 - 应用程序当时正在做什么?
- 分析您的代码库是否可能滥用阻塞调用和同步 methods/uses。
- 检查线程池的最大大小和工作队列实现以及达到限制时的策略(例如,了解 RejectedExecutionHandler 的实现)
无法合理比较这些线程转储,因为它们是通过不同的方式获得的。第一个是使用可维护性代理在 "forced" 模式 (-F) 中获取的。第二个是通过 Attach API 获取的 "normal" 转储。差异解释 here.
输出的含义也不同。 "Normal" dump 显示 java.lang.Thread
对象的状态,而 "forced" dump 显示相应 VM 线程的状态。从 JVM 的角度来看,线程可以处于 IN_NATIVE
、IN_VM
、IN_JAVA
状态、过渡状态或 BLOCKED
状态之一。 BLOCKED
基本上是指任何不可运行的状态,包括线程处于休眠、等待或停放状态。
在您的第一个转储中,BLOCKED
个线程在 Unsafe.park
方法内 - 似乎它们只是闲置,不太可能导致问题。
WAITING
或TIMED_WAITING
是Java级别Thread.State
的值。您只能在 "normal" 转储中看到它们,即没有 -F
选项。
当您无法进行 "normal" 转储时,这通常意味着目标 JVM 正忙于长时间的 运行 安全点操作(例如 Full GC),或者某个进程正在执行没有收到 CPU 时间(例如,内存不足并开始交换)。 OS 像 perf
这样的级别分析器在这种情况下很有用。
我有一个连接到 Hazelcast 的应用程序。最近我发现对 hazelcast 的请求最终开始变得无响应,因此,我对 Hazelcast 进程进行了线程转储。在分析来自开发和生产环境的线程转储时,我发现池中等待任务的线程在不同的环境中处于不同的状态。
在生产服务器上,线程被阻塞(500 个中有 337 个)。 在开发环境中,没有线程被阻塞(50% 作为 runnable 和 50% 作为 waiting 60 个线程)。
那些阻塞线程是否正在等待某些线程无限期持有的同步块? 500 个线程是否过多(我收到一些分析器的警告)?这会导致我的应用程序无响应吗?
导致此状态的可能原因是什么以及如何解决?
线程转储(生产):
Thread 120713: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.ForkJoinPool.awaitWork(java.util.concurrent.ForkJoinPool$WorkQueue, int) @bci=350, line=1824 (Compiled frame)
- java.util.concurrent.ForkJoinPool.runWorker(java.util.concurrent.ForkJoinPool$WorkQueue) @bci=44, line=1693 (Interpreted frame)
- java.util.concurrent.ForkJoinWorkerThread.run() @bci=24, line=157 (Interpreted frame)
Thread 120743: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=14, line=175 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await() @bci=42, line=2039 (Compiled frame)
- java.util.concurrent.LinkedBlockingQueue.take() @bci=29, line=442 (Compiled frame)
- java.util.concurrent.ThreadPoolExecutor.getTask() @bci=149, line=1074 (Compiled frame)
Thread 120743: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.park() @bci=5, line=304 (Compiled frame)
- com.hazelcast.internal.util.concurrent.MPSCQueue.takeAll() @bci=83, line=231 (Compiled frame)
- com.hazelcast.internal.util.concurrent.MPSCQueue.take() @bci=12, line=153 (Compiled frame)
- com.hazelcast.client.spi.impl.ClientResponseHandlerSupplier$ResponseThread.doRun() @bci=17, line=164 (Compiled
Thread 128753: (state = BLOCKED)
- sun.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
- java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=20, line=215 (Compiled frame)
- java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) @bci=78, line=2078 (Compiled frame)
- java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=124, line=1093 (Compiled frame)
- java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take() @bci=1, line=809 (Compiled frame)
来自开发环境的线程转储:
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c1a1bc38> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
Thread states - 这里是对线程状态的一些解释。
NEW The thread has not yet started.
RUNNABLE The thread is executing in the JVM.
BLOCKED The thread is blocked waiting for a monitor lock.
WAITING The thread is waiting indefinitely for another thread to perform a particular action.
TIMED_WAITING The thread is waiting for another thread to perform an action for up to a specified waiting time.
TERMINATED The thread has exited.
BLOCKED 状态应该关注它是否对相同的线程存在很长时间。 这当然取决于你的情况 - 你如何处理数据,你如何创建线程(和线程池),你的关键部分是什么以及它们如何相互作用。
产品的单线程转储是不够的 - 你应该采取几个转储和 - 比较发生的事情和 - 线程有多长 running/waiting - 这是发生在高负载时还是高负载之后 - 你的线程数是否会随着时间的推移而增加,等等
因此无法判断在这个特定时间点 500 个阻塞的线程是好是坏,但可以肯定的是它是令人担忧的。一个线程大约需要 2MB 来初始化和分配内存,所以它是 1GB 内存。
很可能某些线程占用了一些关键部分,导致您的问题和应用程序无响应。您可能会使用阻塞方法等从队列中读取一些非常复杂的情况。
可能的行动方案:
- 进行几次转储并进行比较 - 发生了什么变化?哪些线程仍被阻塞?
- 检查您是否可以在转储的堆栈跟踪中查明阻塞线程中的调用(在您的包前缀或 java's/hazelcast 的包中)。
- 使用跟踪工具 (flight-recorder / jvisualvm) 检查线程的增长以及线程(被阻塞)的创建时间 - 应用程序当时正在做什么?
- 分析您的代码库是否可能滥用阻塞调用和同步 methods/uses。
- 检查线程池的最大大小和工作队列实现以及达到限制时的策略(例如,了解 RejectedExecutionHandler 的实现)
无法合理比较这些线程转储,因为它们是通过不同的方式获得的。第一个是使用可维护性代理在 "forced" 模式 (-F) 中获取的。第二个是通过 Attach API 获取的 "normal" 转储。差异解释 here.
输出的含义也不同。 "Normal" dump 显示 java.lang.Thread
对象的状态,而 "forced" dump 显示相应 VM 线程的状态。从 JVM 的角度来看,线程可以处于 IN_NATIVE
、IN_VM
、IN_JAVA
状态、过渡状态或 BLOCKED
状态之一。 BLOCKED
基本上是指任何不可运行的状态,包括线程处于休眠、等待或停放状态。
在您的第一个转储中,BLOCKED
个线程在 Unsafe.park
方法内 - 似乎它们只是闲置,不太可能导致问题。
WAITING
或TIMED_WAITING
是Java级别Thread.State
的值。您只能在 "normal" 转储中看到它们,即没有 -F
选项。
当您无法进行 "normal" 转储时,这通常意味着目标 JVM 正忙于长时间的 运行 安全点操作(例如 Full GC),或者某个进程正在执行没有收到 CPU 时间(例如,内存不足并开始交换)。 OS 像 perf
这样的级别分析器在这种情况下很有用。