commons-pool2对象池分配回收策略
前一段时间写了一个rpc的客户端,使用apache commons-pool2的对象池管理长连接。其中由于没有完全弄清楚各个参数的意义,即其中分配回收策略,所以出现了一些不太合理的现象。最近详细看了源码,这里整理一下。
一、对象的创建
1. 对象创建的时机
对象的创建有两个时机,一个是应用像对象池获取对象时;另一个是空闲对象回收后,判断需要保证minIdle数量的对象并重新创建对象。关于第二点在回收时再说,先梳理一下获取对象的过程。
2. 对象创建的条件
如上图所示,对象的创建如果满足下面的判断条件,则直接跳出,不会创建对象。判断条件中涉及到了两个值localMaxTotal 和newCreateCount ,localMaxTotal 就是对象池配置中的maxTotal(最大的对象个数);newCreateCount 表示当前对象池中对象的个数,每当创建一个对象这个数值加一,销毁一个对象这个数值减一。那么创建对象需要满足两个条件:
- 对象池中的对象没有达到上限maxTotal
- 对象池中的对象个数不能超过Integer类型的最大值
GenericObjectPool create方法中:
if (localMaxTotal > -1 && newCreateCount > localMaxTotal ||
newCreateCount > Integer.MAX_VALUE) {
createCount.decrementAndGet();
return null;
}
3. 对象创建之后的工作
对象创建之后还有一个分配的过程。这个过程会把对象的状态修改为ALLOCATED,初始时是IDLE。同时会记录此对象本次被借出的时间、被借出的次数。如下代码所示:
if (state == PooledObjectState.IDLE) {
state = PooledObjectState.ALLOCATED;
lastBorrowTime = System.currentTimeMillis();
lastUseTime = lastBorrowTime;
borrowedCount++;
if (logAbandoned) {
borrowedBy = new AbandonedObjectCreatedException();
}
return true;
}
分配完之后还有一个激活和验证的过程,分别调用factory中自定义的activateObject和validateObject方法。如果激活和验证过程失败,则会销毁这个创建好的对象。
二、对象的回收
1. 空闲对象回收线程的启动
public GenericObjectPool(PooledObjectFactory<T> factory,
GenericObjectPoolConfig config) {
super(config, ONAME_BASE, config.getJmxNamePrefix());
if (factory == null) {
jmxUnregister(); // tidy up
throw new IllegalArgumentException("factory may not be null");
}
this.factory = factory;
idleObjects = new LinkedBlockingDeque<PooledObject<T>>(config.getFairness());
setConfig(config);
startEvictor(getTimeBetweenEvictionRunsMillis());
}
其实即使没有最后面的startEvictor(getTimeBetweenEvictionRunsMillis()),还是会启动evict线程,因为setConfig方法也会间接启动evict线程。
因为从config拷贝配置到对象池时,一定会拷贝timeBetweenEvictionRunsMillis参数,而对象池设置这个参数时就会启动evict线程
public final void setTimeBetweenEvictionRunsMillis(
long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
startEvictor(timeBetweenEvictionRunsMillis);
}
2. 空闲对象回收线程的条件
一个空闲对象是否被回收,关键取决于EvictionPolicy的evict方法,而DefaultEvictionPolicy是它的唯一实现:
public boolean evict(EvictionConfig config, PooledObject<T> underTest,
int idleCount) {
if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
config.getMinIdle() < idleCount) ||
config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
return true;
}
return false;
}
可以看到,有两个参数影响了是否回收的结果,config.getIdleSoftEvictTime()和config.getIdleEvictTime()。他们对应连接池配置的softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis参数,只是在判断对象是否可回收前做了一些特殊的处理,如果设置了负数值,则会被转换为Long的最大值:
public EvictionConfig(long poolIdleEvictTime, long poolIdleSoftEvictTime,
int minIdle) {
if (poolIdleEvictTime > 0) {
idleEvictTime = poolIdleEvictTime;
} else {
idleEvictTime = Long.MAX_VALUE;
}
if (poolIdleSoftEvictTime > 0) {
idleSoftEvictTime = poolIdleSoftEvictTime;
} else {
idleSoftEvictTime = Long.MAX_VALUE;
}
this.minIdle = minIdle;
}
softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis的两个判断条件只要满足其中一条即表示此对象可被回收。它们的区别在于空闲时间大于softMinEvictableIdleTimeMillis时,空闲对象的数量一定要大于配置的minIdle才可以;而minEvictableIdleTimeMillis只要大于空闲时间就成立。
所以,minEvictableIdleTimeMillis会暴力回收调空闲队列里的对象,不管空闲个数是否超过minIdle。而之后还会重新创建,导致对象被销毁又重建的现象发生。
3. 空闲对象回收线程其他的工作
evict线程不光做回收对象的工作。
1. 刚刚提到,如果回收后发现空闲队列中的对象个数已经小于minIdle,则会创建对象至minIdle个。
2. 如果不满足回收条件,则可以检查对象可用性,取决于testWhileIdle参数。检查的过程是先激活、再验证,如果失败则销毁对象。
4. 对象的销毁途径
- 空闲对象直接被evict线程回收
- borrow、return、evict过程中对象的激活、验证、挂起等操作出现异常
- return对象时对象池关闭或者maxIdle到了上限
- 应用主动调用invalidateObject或clear方法