大量创建连接池导致的线程泄漏

234 阅读1分钟

对的, 你没看错, 大量创建连接池, 连接是稳定的, 但是却导致线程暴涨.

症状

这是一个Java 应用, 有监控它的线程总数, 各个服务器的线程总数看上去是这个样子的:

thread.png

查看不断增长的线程

通过thread dump, 我们可以看到里面包含居多以 Connection evictor命名的线程, 如下:

thread2.png

还碰巧看到一个正在做事的线程栈, 从这里我们可以大概猜出这是 Apache httpClient 的连接池, 它正在清理长期不用的连接.

我们推测这个应用在不断的创建连接池, 然后就抛弃不用, 然后就该 Connection evictor 出场来清理这些不再被使用的连接.

验证推断

既然推断创建了很多连接池, 那么我们就来看看到底有多少连接池.

$ jcmd 2447856 GC.class_histogram | grep PoolingHttpClientConnectionManager
 223:           3211          101568  org.apache.http.impl.conn.PoolingHttpClientConnectionManager
 224:           3211          101568  org.apache.http.impl.conn.PoolingHttpClientConnectionManager$ConfigData
 284:           3211          86176  org.apache.http.impl.conn.PoolingHttpClientConnectionManager$InternalConnectionFactory

上面第二列的输出表示有多少这样的对象, 不出所料, 果然有这么多连接池.

找出真凶, 哪里创建这么多连接池

既然创建这么多 PoolingHttpClientConnectionManager 对象, 那么就看看到底哪里新建的这些对象吧. 使用 Btrace 工具, 使用如下Btrace 脚本:

import org.openjdk.btrace.core.annotations.*;
import static org.openjdk.btrace.core.BTraceUtils.*;
 
import org.openjdk.btrace.core.BTraceUtils.Strings;
 
@BTrace
public class ConnectionPoolTracer {
 
    @OnMethod( clazz="/org\\.apache\\.http\\.impl\\.conn\\.PoolingHttpClientConnectionManager/", method="<init>" )
    public static void createPool() {
        println(Strings.strcat("current thread: ", name(currentThread())));
        println(jstackStr());
    }
}

去执行, 得到如下栈:

org.apache.http.impl.conn.PoolingHttpClientConnectionManager.<init>(PoolingHttpClientConnectionManager.java:165)
org.apache.http.impl.client.HttpClientBuilder.build(HttpClientBuilder.java:988)
org.tianxiaohui.utils.HttpConnectionUtils.getHttpConnection(HttpConnectionUtils.java:56)
org.tianxiaohui.utils.HttpUtils.createClientWithSSL(HttpUtils.java:144)
org.tianxiaohui.utils.HttpUtils.createClientWithSSL(HttpUtils.java:128)
org.tianxiaohui.service.CustomProperties.getVariable(CustomProperties.java:1133)

原来是每次调用 HttpConnectionUtils.getHttpConnection() 这个方法, 就会创建一个新的连接池.

修复

全局共享的一个连接池.