1.removeAbandoned
removeAbandoned
功能常用来进行连接泄露检查,该功能共有三个参数进行组合配置。
参数 | 类型 | 默认值 | 含义 |
---|---|---|---|
removeAbandoned | Boolean | false | 是否开启removeAbandoned功能 |
removeAbandonedTimeout | Long | 300000 | 活动时间超过该参数值的连接将被自动回收 |
logAbandoned | Boolean | false | 是否开启日志打印回收信息 |
2.removeAbandoned功能如何实现自动回收长链接
removeAbandoned
功能是由DruidDataSource#removeAbandoned
方法进行处理,该方法将对存储于activeConnections
中的连接进行遍历,通过对DruidPooledConnection
中的相关状态进行跟踪来判断是否满足回收条件。
public int removeAbandoned() {
//驱逐计数
int removeCount = 0;
//得到当前时间
long currrentNanos = System.nanoTime();
//创建List,用于记录需要被驱逐的连接
List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
//加锁,避免出现线程问题
activeConnectionLock.lock();
try {
//以迭代器的形式获得activeConnections的所有Key(即连接对象)
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
//遍历迭代器
for (; iter.hasNext();) {
//获得当前连接
DruidPooledConnection pooledConnection = iter.next();
//如果当前连接正在执行中,则暂不处理
if (pooledConnection.isRunning()) {
continue;
}
//判断该连接存在时间,取值为当前时间-创建连接时间/1000*1000(将纳秒转为毫秒)
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
//如果连接存在时间超过了指定值
if (timeMillis >= removeAbandonedTimeoutMillis) {
//移除该连接
iter.remove();
//取消对该连接的跟踪
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);
//设置状态
pooledConnection.abandond();
//全局自动回收计数自增
removeAbandonedCount++;
//本次自动回收计数自增
removeCount++;
//如果开启了logAbandoned,则拼接日志并进行输出
if (isLogAbandoned()) {
StringBuilder buf = new StringBuilder();
buf.append("abandon connection, owner thread: ");
buf.append(pooledConnection.getOwnerThread().getName());
buf.append(", connected at : ");
buf.append(pooledConnection.getConnectedTimeMillis());
buf.append(", open stackTrace\n");
StackTraceElement[] trace = pooledConnection.getConnectStackTrace();
for (int i = 0; i < trace.length; i++) {
buf.append("\tat ");
buf.append(trace[i].toString());
buf.append("\n");
}
buf.append("ownerThread current state is " + pooledConnection.getOwnerThread().getState()
+ ", current stackTrace\n");
trace = pooledConnection.getOwnerThread().getStackTrace();
for (int i = 0; i < trace.length; i++) {
buf.append("\tat ");
buf.append(trace[i].toString());
buf.append("\n");
}
LOG.error(buf.toString());
}
}
}
//返回本次回收连接数
return removeCount;
}
2.1 removeAbandoned如何判断连接正在执行中
在removeAbandoned()
方法中,存在pooledConnection.isRunning()
判断用于判断连接是否正在执行中,该方法取值为连接对象中的一个内部状态,该状态标记了当前连接是否正在执行,这里以DruidPooledPreparedStatement#executeQuery()
执行进行分析。
@Override
public ResultSet executeQuery() throws SQLException {
···省略部分代码···
//此处调用连接对象的beforeExecute()方法
conn.beforeExecute();
try {
ResultSet rs = stmt.executeQuery();
if (rs == null) {
return null;
}
DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
addResultSetTrace(poolableResultSet);
return poolableResultSet;
} catch (Throwable t) {
errorCheck(t);
throw checkException(t);
} finally {
//调用连接对象的afterExecute()方法
conn.afterExecute();
}
···省略部分代码···
}
此时,推测有关running
状态跟踪是通过beforeExecute()
与afterExecute()
进行跟踪。
final void beforeExecute() {
//得到当前连接的持有者
final DruidConnectionHolder holder = this.holder;
//持有者非空并且开启了removeAbandoned功能
if (holder != null && holder.dataSource.removeAbandoned) {
//设置当前连接正在执行中
running = true;
}
}
final void afterExecute() {
//得到当前连接的持有者
final DruidConnectionHolder holder = this.holder;
//持有者非空
if (holder != null) {
//获得连接池对象
DruidAbstractDataSource dataSource = holder.dataSource;
//判断是否开启removeAbandoned
if (dataSource.removeAbandoned) {
//设置当前连接不在执行中
running = false;
//设置最后活跃时间
holder.lastActiveTimeMillis = System.currentTimeMillis();
}
//更新连接池onFatalError状态
dataSource.onFatalError = false;
}
}
由此可知,开启removeAbandoned
后,连接将在执行前后对running
状态进行变更,以确定当前SQL是否正在执行中。