最近研究一个 Java 应用程序的性能问题, 当观察该应用程序的线程的时候, 发现有很多 ForkJoinPool 的 worker 线程, 这种线程数量很大, 并从线程的编号来看, 该应用创建了非常多的这样线程, 然后它们又逐渐被销毁.
下面是一个 Thread dump 的截图, 从截图中可以看到, 这种线程基本处于 2 种状态: TERMINATED 和 TIMED_WAITING. 理论来讲, 应该还有 Runnable 状态的, 只是没有出现在我们的 thread dump 中.
通过 Btrace 代码注入, 我们很容易找到了错误使用线程池的代码:
这段代码在一个 Stream 的代码块中, 使用 Executors.newWorkStealingPool() 创建了一个 ForkJoinPool. 意味着每次调用这个方法, 都要创建一个新的 ForkJoinPool. 然而这正是线程池本来设计要解决的问题: 不要重复创建线程, 要复用.
所以, 解决办法就是替换这个线程池的创建和使用方式, 使用应用生命周期内复用线程的方法.
虽然上面的问题解决了, 那么还有个疑惑的地方: 我们看到上面线程池的编号是一个非常大的数字, 说明它已经被创建了这么多线程池了, 可是我们现在活着的总的线程数才几千, 说明早期创建的线程池都已经消亡了. 从那些 worker 的状态: TERMINATED 来看, 这些线程正在死亡, 最终线程池也会消亡.
一般情况下, 想让一个线程池终止, 我们都是通过调用线程池的 shutdown() 方法或者 shutdownNow() 方法. 可是我们观察上面线程池创建的地方, 并没有任何对象引用这个线程池, 那么是哪里调用这个 shutdown() 或者 shutdownNow() 方法的呢? 看上去这是一个矛盾的问题: 没用人调用 shutdown() 或者 shutdownNow(), 但是这些智能的 worker 线程却在慢慢消亡.
为了查清 worker 线程智能消亡的问题, 我们写了如下的 Btrace 代码来查找 worker 线程消亡的原因:
import static org.openjdk.btrace.core.BTraceUtils.jstackStr;
import static org.openjdk.btrace.core.BTraceUtils.println;
import org.openjdk.btrace.core.annotations.BTrace;
import org.openjdk.btrace.core.annotations.OnMethod;
@BTrace
public class ForkJoinPoolTrace {
/**
* 用来查找那里创建了 ForkJoinPool
@OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/<init>/" )
public static void createPool() {
println(jstackStr());
}**/
@OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/shutdown/" )
public static void shutdownPool() {
println(jstackStr());
}
@OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/tryTerminate/" )
public static void tryTerminate() {
println(jstackStr());
}
@OnMethod( clazz="/java\\.util\\.concurrent\\.ForkJoinPool/", method="/shutdownNow/" )
public static void shutdownPoolNow() {
println(jstackStr());
}
}
最终找到了让 worker 线程消亡的原因: 是在 awaitWorker() 方法中使用了 tryTerminate() 方法.
java.util.concurrent.ForkJoinPool.tryTerminate(ForkJoinPool.java)
java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1807)
java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1693)
java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)
其实终止的原因就是: 如果 worker 线程发现很久没有活可以干, 那么就 自我终止吧.
ForkJoinPool.awaitWork 方法:
可能有人会问, 线程消亡了, 那么创建的那些 ForkJoinPool 呢? 这些对象要靠 Thread 活着, 如果那些 Worker Thread 都终止了, 那么这些对象必然会被回收掉.