1、traceEnable与abandoned属性
先看下tranceEnable和abandoned这两个DruidDataSource类中的成员变量
traceEnable和abandoned都是DruidPooledConnection对象的成员属性
traceEnable:在获取连接getConnectionDirect时判断如果开启了removeAbandoned那么这个连接的traceEnable=true,防止在发生removeAbandoned时,再次遍历到这个连接对象。
// com.alibaba.druid.pool.DruidDataSource#recycle
// DCL双重锁检测,防止在removeAbandoned时再次遍历到这个连接
if (pooledConnection.traceEnable) {
Object oldInfo = null;
activeConnectionLock.lock();
try {
if (pooledConnection.traceEnable) {
// traceEnable=true时,将连接从activeConnections中移除这个连接
// 防止在removeAbandoned时再次遍历到这个连接
oldInfo = activeConnections.remove(pooledConnection);
pooledConnection.traceEnable = false;
}
} finally {
activeConnectionLock.unlock();
}
if (oldInfo == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
}
}
}
// com.alibaba.druid.pool.DruidDataSource#removeAbandoned
// removeAbandonedTimeoutMillis: 连接回收的超时时间,默认300秒
// 如果当前连接使用的时间 大于 超时时间阈值(默认5分钟),则该连接将被回收
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
// 监控连接参数置为false,在removeAbandoned=false时,traceEnable永远是false
// getConnectionDirect时如果removeAbandoned=true时,给连接赋值traceEnable=true
// com.alibaba.druid.pool.DruidDataSource.recycle回收连接时,将traceEnable=false
pooledConnection.setTraceEnable(false);
// 废弃连接存入容器
abandonedList.add(pooledConnection);
}
abandoned:在开启removeAbandoned时,判断如果当前从connections连接池借出去的连接使用时长是否超过阈值(默认5分钟),超过阈值的连接被打上了abandoned=true的标记,那么在close连接时,这些连接会被丢弃而不会再次回收,目的是防止连接执行时间过长带来的内存泄漏,如果连接时间过长在数据库端会产生大量的内存而且无法回收。
public void recycle() throws SQLException {
。。。
// 没有开启removeAbandoned,则不会直接丢掉连接,而是进入dataSource.recycle阶段,将连接重新放入连接池
if (!this.abandoned) {
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
}
// 断掉与holder的关联,监控线程不会走上面的回收流程,直接断掉
this.holder = null;
conn = null;
transactionInfo = null;
closed = true;
}
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano();
poolableConnection.traceEnable = true;
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT);
} finally {
activeConnectionLock.unlock();
}
}
traceEnable防止连接重复被removeAbandoned机制从activeConnections数组中检测到;
abandoned为true时说明当前连接将被直接丢弃
2、removeAbandoned机制
removeAbandoned:连接泄漏检测机制
防止内存泄漏,如果连接长期没有close,那么在数据库中当前连接用到的一些资源无法被释放导致数据库的压力增加,而且activeConnections(从连接池中借出去的连接),无法将连接放回connections,带来的问题就是无法生产新连接,业务线程会阻塞;
在removeAbandoned机制中会把借出去的连接超过阈值(默认5分钟)的连接放入abandonedList容器中,并且把这些连接的abandoned属性设置为true,这些连接将别直接丢弃。
/*
连接泄漏检测机制
防止内存泄漏,activeConnections是保存从连接池借出去的连接,如果连接长期没有close,那么
activeConnections中的连接无法放回connections,带来的问题就是无法生产新连接,
所以在removeAbandoned机制中,会把超过阈值的连接放入abandonedList中,并且把他们的abandoned属性
标记为true,
在com.alibaba.druid.pool.DruidPooledConnection.recycle中会判断abandoned这个变量,如果为
true,那么不会进行连接回收操作,而是直接丢弃掉
*/
public int removeAbandoned() {
// 移除连接计数器
int removeCount = 0;
long currrentNanos = System.nanoTime();
// 初始化存放被丢弃连接的容器
List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
/*
com.alibaba.druid.pool.DruidAbstractDataSource.getActiveConnections
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect
com.alibaba.druid.pool.DruidDataSource.handleFatalError
com.alibaba.druid.pool.DruidDataSource.recycle
com.alibaba.druid.pool.DruidDataSource.removeAbandoned
*/
activeConnectionLock.lock();
try {
// 获取正在使用的连接集合
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
// 遍历正在使用的连接
for (; iter.hasNext(); ) {
DruidPooledConnection pooledConnection = iter.next();
// 判断改连接是否还在运行,不运行则回收
if (pooledConnection.isRunning()) {
continue;
}
// 当前连接已经使用的时间
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
// removeAbandonedTimeoutMillis: 连接回收的超时时间,默认300秒
// 如果当前连接使用的时间 大于 超时时间阈值(默认5分钟),则该连接将被回收
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
// 监控连接参数置为false,在removeAbandoned=false时,traceEnable永远是false
// getConnectionDirect时如果removeAbandoned=true时,给连接赋值traceEnable=true
// com.alibaba.druid.pool.DruidDataSource.recycle回收连接时,将traceEnable=false
pooledConnection.setTraceEnable(false);
// 废弃连接存入容器
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
// 执行丢弃连接逻辑
if (abandonedList.size() > 0) {
for (DruidPooledConnection pooledConnection : abandonedList) {
final ReentrantLock lock = pooledConnection.lock;
lock.lock();
try {
if (pooledConnection.isDisable()) {
continue;
}
} finally {
lock.unlock();
}
JdbcUtils.close(pooledConnection);
// 这个被DruidPooledConnection封装的连接将被丢弃
pooledConnection.abandond();
removeAbandonedCount++;
removeCount++;
//日志代码暂时跳过不分析
。。。
}
}
return removeCount;
}
销毁连接的守护线程DestroyCOnnectionThread会每隔(默认1分钟)一段时间调用destroyTask线程的run方法,destroyTask会判断是否开启removeAbandoned机制。
removeAbandoned只会回收不活跃的连接,正在running的连接不回收
// 判断连接是否还在运行,不运行则回收 if (pooledConnection.isRunning()) { continue; }
总结
removeAbandoned是一个连接泄漏检测机制,能够将执行未关闭的连接检测出来,但是官方建议不开启,我个人觉得是假如一个conn上一次使用未关闭,又执行了一个sql那么可能会超时这时候如果强行关闭链接的话会抛出connection closed异常,那么业务就崩溃了,所以首先要保证业务侧能正常运行,不开启removeAbandoned。