读取线程转储以调试耗尽的数据库连接池

时间:2014-05-04 03:07:45

标签: java mysql connection-pooling

我有一个由jetty + mysql提供服务的网络应用程序。我遇到了数据库连接池耗尽的问题,并且所有线程都开始阻塞等待连接。我试过两个数据库连接池库:(1)bonecp(2)hikari。两者都表现出与我的应用相同的行为。

当我看到这种状态时,我已经完成了几次线程转储,并且所有被阻塞的线程都处于这种状态(不选择在bonecp上,我确定它现在就在我的最后):

"qtp1218743501-131" prio=10 tid=0x00007fb858295800 nid=0x669b waiting on condition [0x00007fb8cd5d3000]
  java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x0000000763f42d20> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
    at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
    at com.jolbox.bonecp.DefaultConnectionStrategy.getConnectionInternal(DefaultConnectionStrategy.java:82)
    at com.jolbox.bonecp.AbstractConnectionStrategy.getConnection(AbstractConnectionStrategy.java:90)
    at com.jolbox.bonecp.BoneCP.getConnection(BoneCP.java:553)
    at com.me.Foo.start(Foo.java:30)
    ...

我不知道从哪里开始。我在想,我会在线程转储中看到一些堆栈跟踪,我的代码卡在那里做了一些长时间的操作,而不是等待连接。例如,如果我的代码如下所示:

public class Foo {
    public void start() {
        Connection conn = threadPool.getConnection();
        work(conn);
        conn.close();
    }

    public void work(Connection conn) {
        .. something lengthy like scan every row in the database etc ..
    }
}

我希望上面的一个线程有一个堆栈跟踪,显示它在work()方法中工作:

...
at com.me.mycode.Foo.work()
at com.me.mycode.Foo.start()

但他们只是在等待连接:

...
at com.jolbox.bonecp.BoneCP.getConnection() // ?
at com.me.mycode.Foo.work()
at com.me.mycode.Foo.start()

关于如何继续调试的任何想法都会很棒。

其他一些背景:该应用程序正常运行约45分钟,内存和线程转储显示任何不寻常的东西。然后触发条件并且线程计数突然增加。我开始认为它可能是应用程序尝试执行的sql语句的某种组合,这会转换为mysql端的某种锁定,但我再次希望上面的堆栈跟踪中的一些线程向我显示它们是在那部分代码中。

使用visualvm进行了线程转储。

由于

2 个答案:

答案 0 :(得分:0)

我质疑整个设计。

如果多线程netIO中有等待块,则需要更好地实现连接。

我建议你看看非阻塞IO(Java.nio,渠道包),或者对你的锁进行粒化。

答案 1 :(得分:0)

利用连接池的配置选项(请参阅BoneCPConfig / HikariCPConfig)。首先,设置连接超时(HikariCP connectionTimeout)和泄漏检测超时(HikariCP leakDetectionThreshold,我找不到BoneCP中的对应物)。当某些东西不太正确时,可能会有更多的配置选项转储堆栈跟踪。

我的猜测是你的应用程序并不总是返回到池的连接,并且在45分钟之后池中没有连接(因此阻止永远尝试从池中获得连接)。处理打开/关闭文件之类的连接,即始终使用try / finally:

public void start() {

    Connection conn = null;
    try {
        work(conn = dbPool.getConnection());
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
}

最后,两个连接池都有允许JMX monitoring的选项。您可以使用它来监视池中的奇怪行为。