在启动完成之后,连接池紧接着会进行一个initialize的操作,这一节主要介绍这个initialize所完成的操作。具体涉及到如下几个参数:
< idle-timeout-minutes >:一个连接的最大空闲超时时间,即在连接被关闭之前,连接可以空闲的最长时间,超过这个时间连接就会被关闭。这个参数设置为0则禁用这个功能,文档上说默认值为15分钟,我看的jboss4.2.3的源代码中默认值为30分钟。
< background-validation >:在jboss4.0.5版本中,增加了一个后台连接验证的功能,用于减少RDBMS系统的负载。当使用这个功能的时候,jboss将使用一个独立的线程(ConnectionValidator)去验证当前池中的连接。这个参数必需在设置为true时才能生效,默认设置为false。
< background-validation-minutes >:ConnectionValidator线程被唤醒的定时间隔。默认设置为10分钟。注意:为谨慎起见,设置这个值稍大些,或者小于idle-timeout-minutes。
< background-validation-millis >:从jboss5.0版本开始,代替了background-validation-minutes参数。参数background-validation-minutes不再被支持。同时background-validation这个参数也被废弃。只要配置了background-validation-millis > 0,则启用后台验证。更多内容查看:https://jira.jboss.org/browse/JBAS-4088。
连接池的初始化方法如下:
/**
* Initialize the pool
*/
protected void initialize()
{
/*将一个连接池对象注册到IdleRemover线程中,表示这个连接池使用IdleRemover来进行管理。
IdleRemover线程是空闲连接清理线程,被唤醒的周期是poolParams.idleTimeout/2。
即配置的idle-timeout-minutes参数/2。
默认idle-timeout-minutes为30分钟,所以清理线程是15分钟运行一次。
*/
if (poolParams.idleTimeout != 0)
IdleRemover.registerPool(this, poolParams.idleTimeout);
/*将一个连接池对象注册到ConnectionValidator线程中,表示这个连接池使用
ConnectionValidator来进行管理。IdleRemover线程是一个验证连接池的线程,
被唤醒的周期是poolParams.backgroundValidation/2。
即配置的background-validation-millis 参数/2。
默认background-validation-millis为10分钟,所以清理线程是5分钟运行一次。
*/
if (poolParams.backgroundValidation)
{
log.debug("Registering for background validation at interval "
+ poolParams.backgroundInterval);
ConnectionValidator.registerPool(this, poolParams.backgroundInterval);
}
}
IdleRemover和 ConnectionValidator两个线程的处理方式是一致的。定时调度的处理方式也是完全一致的。
区别在于,定时任务的启动IdleRemover是15分钟清理一次空闲的连接,而ConnectionValidator是5分钟进行一次连接验证,后面会给出主要的两个方法。
因为两者调度方式一致,这里以IdleRemover类为例,来看看这两个线程的具体调度算法:
.......
private static final IdleRemover remover = new IdleRemover();
//注册一个连接池清理对象,即将一个连接池清理对象加入到pools中,并传入一个清理的时间参数
public static void registerPool(InternalManagedConnectionPool mcp, long interval)
{
remover.internalRegisterPool(mcp, interval);
}
//反注册一个连接池清理对象,并且设置interval = Long.MAX_VALUE
public static void unregisterPool(InternalManagedConnectionPool mcp)
{
remover.internalUnregisterPool(mcp);
}
.......
IdleRemover线程的启动:
private IdleRemover ()
{
AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
Runnable runnable = new IdleRemoverRunnable();
Thread removerThread = new Thread(runnable, "IdleRemover");
removerThread.setDaemon(true);
removerThread.start();
return null;
}
});
}
线程的run方法如下:
/**
* Idle Remover background thread
*/
private class IdleRemoverRunnable implements Runnable
{
public void run()
{
//更改上下文ClassLoader.
setupContextClassLoader();
//这是一个线程安全的操作。
synchronized (pools)
{
while (true)
{
try
{
/*
* interval在这个类中的初始值是一个无限大的值:interval = Long.MAX_VALUE。
即线程开始运行时,即一直wait在这里。
当执行internalRegisterPool方法,即第一次被唤醒时,
interval即被设置为传入的interval/2,
如果idle-time-out设置为30分钟,这个interval的值即被设为15分钟。
即每次wait15分钟,表示15分钟线程被唤醒清理一次idle的连接。
*/
pools.wait(interval);
log.debug("run: IdleRemover notifying pools, interval: " + interval);
/*
* 这里的pools和第二节中讲到的pools一致,是一个临时队列,区别是这个临时队列
不会进行清理,只有调用internalUnregisterPool方法才会从队列中清理出去。
*遍历pool中的连接池对象,对所有的连接池对象,执行removeTimeout,后面会详细介绍。
*/
for (Iterator i = pools.iterator(); i.hasNext(); )
((InternalManagedConnectionPool)i.next()).removeTimedOut();
//设置next值,这个next表示pools下一次需要清理的时间点。
next = System.currentTimeMillis() + interval;
if (next < 0)
next = Long.MAX_VALUE;
}
catch (InterruptedException ie)
{
log.info("run: IdleRemover has been interrupted, returning");
return;
}
catch (RuntimeException e)
{
log.warn("run: IdleRemover ignored unexpected runtime exception", e);
}
catch (Error e)
{
log.warn("run: IdleRemover ignored unexpected error", e);
}
}
}
}
}
注册连接池的方法如下:
private void internalRegisterPool(InternalManagedConnectionPool mcp, long interval)
{
log.debug("internalRegisterPool: registering pool with interval "
+ interval + " old interval: " + this.interval);
synchronized (pools)
{
//往pool里面增加需要清理的连接池对象,即注册连接池对象。
pools.add(mcp);
/*这里有两个条件:
* 1.interval>1,即idle-time-out设置至少为2分钟,pools才会执行nofity()。
* 2.interval/2 < LONG.MAX_VALUE,防止idle-time-out设置过大。
*/
if (interval > 1 && interval/2 < this.interval)
{
//设置为interval为interval/2,即清理线程被唤醒的间隔时间。
this.interval = interval/2;
//本连接池下一次可能清理的时间。
long maybeNext = System.currentTimeMillis() + this.interval;
/*如果next即wait线程的下一次唤醒的时间>maybeNext的时间。
即立即唤醒清理线程,进行第一次清理。
这样的目的是为了让这个连接池的注册能够立即生效,而不被旧的interval影响。
*/
if (next > maybeNext && maybeNext > 0)
{
next = maybeNext;
log.debug("internalRegisterPool: about to notify thread:
old next: " + next + ", new next: " + maybeNext);
pools.notify();
}
}
}
}
反注册方法,即从pools队列中去除注册的连接池,表示这个连接池不需要进行清理。
private void internalUnregisterPool(InternalManagedConnectionPool mcp)
{
synchronized (pools)
{
pools.remove(mcp);
if (pools.size() == 0)
{
log.debug("internalUnregisterPool: setting interval to Long.MAX_VALUE");
interval = Long.MAX_VALUE;
}
}
}
以上是IdleRemover.registerPool和 ConnectionValidator.registerPool.registerPool两个线程的调度方式,下面来看一下这两个方法执行的具体操作。
IdleRemover对空闲连接的清理:
public void removeTimedOut()
{
//合建一个清理队列
ArrayList destroy = null;
long timeout = System.currentTimeMillis() - poolParams.idleTimeout;
while (true)
{
synchronized (cls)
{
// 如果连接池中没有连接,则直接返回。
if (cls.size() == 0)
break;
/*
* 获取cls中的第一个连接,即头部的连接。
* 后面一节中会讲到,在getconnection时,都是从cls的尾部获取。
* 所以,cls头部的连接,肯定是最近最少被使用的。
*/
ConnectionListener cl = (ConnectionListener) cls.get(0);
//判断是否超时,return lastUse < timeout
if (cl.isTimedOut(timeout))
{
//销毁连接计数
connectionCounter.incTimedOut();
// 销毁这个连接,并加入到销毁队列
cls.remove(0);
if (destroy == null)
destroy = new ArrayList();
destroy.add(cl);
}
else
{
//它们是按照时间顺序插入的, 如果这个连接没有超时,肯定没有其它的连接超时。
break;
}
}
}
// 有需要销毁的连接,将这些连接进行销毁,并对销毁的连接数进行计数。
if (destroy != null)
{
for (int i = 0; i < destroy.size(); ++i)
{
ConnectionListener cl = (ConnectionListener) destroy.get(i);
if (trace)
log.trace("Destroying timedout connection " + cl);
doDestroy(cl);
}
// 销毁完空闲的连接后,判断连接池没有shutdown,并且最小值大于0。
//进行将连接池填充到最小值的操作。
if (shutdown.get() == false && poolParams.minSize > 0)
PoolFiller.fillPool(this);
}
}
关于fillPool方法可以参考第二节:http://www.dbafree.net/?p=300
关于IdleRemover线程的具体执行过程如下:

ConnectionValidator线程对于连接的验证:
public void validateConnections() throws Exception
{
if (trace)
log.trace("Attempting to validate connections for pool " + this);
//获取信号量,若不能获取,表时当前的连接都在被使用。直接结束validate。
if (permits.attempt(poolParams.blockingTimeout))
{
boolean destroyed = false;
try
{
while (true)
{
ConnectionListener cl = null;
synchronized (cls)
{
if (cls.size() == 0)
{
break;
}
//对连接池中的每个连接进行check,将removeForFrequencyCheck方法见下面。
cl = removeForFrequencyCheck();
}
if (cl == null)
{
break;
}
try
{
Set candidateSet = Collections.singleton(cl.getManagedConnection());
//当前时间-上一次check时间>=后台的validate时间的连接,进行validating操作。
if (mcf instanceof ValidatingManagedConnectionFactory)
{
ValidatingManagedConnectionFactory vcf =
(ValidatingManagedConnectionFactory) mcf;
candidateSet = vcf.getInvalidConnections(candidateSet);
if (candidateSet != null && candidateSet.size() > 0)
{
if (cl.getState() != ConnectionListener.DESTROY)
{
doDestroy(cl);
destroyed = true;
}
}
}
else
{
log.warn("warning: background validation was specified with
a non compliant ManagedConnectionFactory interface.");
}
}
finally
{
if(!destroyed)
{
synchronized (cls)
{
returnForFrequencyCheck(cl);
}
}
}
}
}
finally
{
permits.release();
//destory之后,也进行一个fillPool的操作,这个操作可以参考第二节的内容
if (destroyed && shutdown.get() == false && poolParams.minSize > 0)
{
PoolFiller.fillPool(this);
}
}
}
}
这个validate操作,由前台的参数控制,可以传入自定义的SQL来验证。也可以直接调用jdbc的ping database操作,如调用ping database方法,通过jdbc驱动中可以找到,oracle执行的是select * from dual来验证,不一一列举。见下面的截图vaildate的方法:

removeForFrequencyCheck方法如下:
private ConnectionListener removeForFrequencyCheck()
{
log.debug("Checking for connection within frequency");
ConnectionListener cl = null;
for (Iterator iter = cls.iterator(); iter.hasNext();)
{
cl = (ConnectionListener) iter.next();
long lastCheck = cl.getLastValidatedTime();
//返回 当前时间 - 上一次check时间 >= 设置的validate时间的第一个连接。
//即表示这个连接没有在backgroundInterval区间内进行check。
if ((System.currentTimeMillis() - lastCheck)
>= poolParams.backgroundInterval)
{
cls.remove(cl);
break;
}
else
{
cl = null;
}
}
return cl;
}
连接池的shutdown
public void shutdown() {
//设置shutdown标志位为true
shutdown.set(true);
//清除IdleRemover线程和ConnectionValidator线程的初始化
IdleRemover.unregisterPool(this);
ConnectionValidator.unRegisterPool(this);
//destroy所有checkout队列和连接临听队列中的连接
flush();
}
flush()函数清理所有的连接:包括checkd out队列(已经被使用)中的连接及空闲的队列的连接。这里将空闲的队列的连接监听进行直接销毁,而checkd out队列的连接设置为DESTROY状态,并没有进行销毁。这是一个安全的操作,我们在下一节的returnConnection方法中可以看到对于DESTROY状态连接的清理。
public void flush() {
//生成一个destroy队列
ArrayList destroy = null;
synchronized (cls) {
if (trace)
log.trace("Flushing pool checkedOut=" + checkedOut + " inPool="
+ cls);
// 标记checkd out的连接为清理的状态。
for (Iterator i = checkedOut.iterator(); i.hasNext();) {
ConnectionListener cl = (ConnectionListener) i.next();
if (trace)
log
.trace("Flush marking checked out connection for destruction "
+ cl);
cl.setState(ConnectionListener.DESTROY);
}
// 销毁空闲的,需要清理的连接监听器,并加入到destroy队列中。
while (cls.size() > 0) {
ConnectionListener cl = (ConnectionListener) cls.remove(0);
if (destroy == null)
destroy = new ArrayList();
destroy.add(cl);
}
}
//销毁destory队列中的所有连接临听。
if (destroy != null) {
for (int i = 0; i < destroy.size(); ++i) {
ConnectionListener cl = (ConnectionListener) destroy.get(i);
if (trace)
log.trace("Destroying flushed connection " + cl);
doDestroy(cl);
}
// 如果shutdown==false,即连接池没有shutdown,则再执行一个fillPool的操作
//将连接数填充到min值。
if (shutdown.get() == false && poolParams.minSize > 0)
PoolFiller.fillPool(this);
}
}
关于shutdown操作,流程图如下:

总结:
idle-timeout-minutes参数:后台定时清理过度空闲的连接,从而节省数据库的连接资源,相应线程的wait时间为idle-timeout-minutes/2。
background-validation-millis参数:后台定时验证连接是否有效,对于oracle,内部执行一个select * from dual;的方法来进行验证,相应线程的wait时间为background-validation-millis/2,JBOSS 4的版本中,默认不启用这个验证。
JBOSS会各自启动一个线程来为这两个参数工作,线程内部的调度机制完全一致。IdleRemover线程被唤醒(即每隔多少时间执行)的区间是idle-timeout-minutes参数/2,ConnectionValidator线程被唤醒(即每隔多少时间执行)的区间是background-validation-millis/2。
在销毁空闲的连接(IdleRemover)和无效的连接(ConnectionValidator)后,都会执行一个prefill的操作,将连接池中的连接数填充到min值,所以,对于连接池min需要合理的进行设置,如果min设置过大,JBOSS会将连接不断的进行销毁->创建->销毁->创建…(idle线程对空闲连接销毁,销毁后小于min值,然后马上又创建,新创建的连接处于空闲状态,于是又被销毁…)
总之,我们需要合理的设置连接池min值,对于某些系统来说,数据库的连接资源是很昂贵的。
前段日子在公司的核心系统上优化的一个连接池,主要也是对于min值的优化:一个生产库的JBOSS连接池调整优化及分析。对于连接数的调优,后面会专门整理一篇文章。
1