Hikari连接池——java.lang.Exception: Apparent connection leak detected
- 问题分析
- 总结
问题分析
首先,先看报错:
java.lang.Exception: Apparent connection leak detectedat com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128) ~[HikariCP-3.4.5.jar:?]at com.skyline.MyTest.getConnection(MyTest.java:36)at java.lang.Thread.run(Thread.java:748) [?:1.8.0_261]
Apparent connection leak detected
,网上很多解释都说是连接泄露,但是这个报错就等于连接泄漏了吗?先说答案:并不是!!!
首先,这个报错是从ProxyLeakTask
中抛出来的,源码不多,先贴出来
class ProxyLeakTask implements Runnable
{private static final Logger LOGGER = LoggerFactory.getLogger(ProxyLeakTask.class);static final ProxyLeakTask NO_LEAK;//调度器private ScheduledFuture<?> scheduledFuture;private String connectionName;//异常对象private Exception exception;//线程名private String threadName; //是否泄露private boolean isLeaked;static{//创建一个不检测连接泄漏的taskNO_LEAK = new ProxyLeakTask() {@Overridevoid schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {}@Overridepublic void run() {}@Overridepublic void cancel() {}};}//构造器,需要传入连接池中的连接对象ProxyLeakTask(final PoolEntry poolEntry){//※重点来了,这时创建一个异常对象,当ProxyLeakTask被new出来的时候,就是连接被业务代码申请的时候this.exception = new Exception("Apparent connection leak detected");this.threadName = Thread.currentThread().getName();this.connectionName = poolEntry.connection.toString();}private ProxyLeakTask(){}//※很重要,开始调度到倒计时开始void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold){//拿到这个返回值是为了将来可以cancel这个调度scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);}/** {@inheritDoc} */@Overridepublic void run(){//当代码执行到这里时,说明上面的调度没有被cancel,//那么Hikari就认为之前申请的连接可能已经泄露了,//这个run方法是由executorService回调的isLeaked = true;final StackTraceElement[] stackTrace = exception.getStackTrace(); final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];System.arraycopy(stackTrace, 5, trace, 0, trace.length);exception.setStackTrace(trace);LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);}//取消调度,这个方法会在连接被释放时回调void cancel(){//先把这个调度任务取消scheduledFuture.cancel(false);if (isLeaked) {//很遗憾,如果之前调度任务已经走过了,那么就再打个日志说明一下//之前那个被认为已经泄露了的连接会到池子里了LOGGER.info("Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)", connectionName, threadName);}}
}
接下来我们看下ProxyLeakTask是什么时候被创建的,首先,我们来到这个方法:com.zaxxer.hikari.pool.HikariPool#getConnection(long)
这个是从Hikari中获取连接的方法,大家都知道,但凡你要操作数据库,必然先取连接。我们重点关注一下这个方法 poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
一层一层的看,我们先看这个leakTaskFactory.schedule
方法:
ProxyLeakTask schedule(final PoolEntry poolEntry)
{//先判断leakDetectionThreshold是不是等于0,//如果等于0,返回最一开始那个不检查连接泄露的Task,//如果不等于0,就通过scheduleNewTask创建一下return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);}
重头戏scheduleNewTask
来了
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {//创建一个taskProxyLeakTask task = new ProxyLeakTask(poolEntry);//开始调度,※注意,这里就开始倒计时了!!!!task.schedule(executorService, leakDetectionThreshold);return task;
}
那么poolEntry.createProxyConnection
返回的是个什么对象呢?答案是com.zaxxer.hikari.pool.ProxyConnection
这里肯定是代理了。我们重点关注一下连接释放的方法吧:
看到没有,当连接被释放时,会回调leakTask的cancel方法。
总结
leakDetectionThreshold
如果等于0,那么Hikari就不会做连接泄露的检查leakDetectionThreshold
的时间=从获取调用getConnection()
开始到调用connection.close()
的时间,注意,如果从数据库中获取真正的连接的耗时也是计算在内的!Apparent connection leak detected
并不等于连接泄露,我理解只能是猜测可能出现了连接泄露,如果日志中可以找到成对的Previously reported leaked connection {} on thread {} was returned to the pool (unleaked)
那么可以确定的是,连接并没有泄露,只是连接的使用时长>leakDetectionThreshold
了,虽然不是连接泄露,但是可能是执行慢的SQL,也是需要进一步跟踪排查的。- 这个连接泄露的检查机制真的很厉害,从来没想过调度任务可以这么玩,确实有意思,打开了新世界的大门。
引申一下,leakDetectionThreshold的配置项在spring.datasource.hikari.leak-detection-threshold
单位是毫秒