为什么会有对象池
在实际的应用工程当中,存在一些被频繁使用的、创建或者销毁比较耗时、持有的资源也比较昂贵的一些对象。比如:数据库连接对象、线程对象。所以如果能够通过一种方式,把这类对象统一管理,让这类对象可以被循环利用的话,就可以减少很多系统开销(内存、CPU、IO等),极大的提升应用性能。
Apache Commons Pool
Apache Commons Pool就是一个对象池的框架,他提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。Apache Commons Pool是很多连接池实现的基础,比如DBCP连接池、Jedis连接池等。
Apache Commons Pool有个两个大版本,commons-pool和commons-pool2。commons-pool2是对commons-pool的重构,里面大部分核心逻辑实现都是完全重写的。我们所有的源码分析都是基于commons-pool2。
在commons-pool2中,对象池的核心接口叫做ObjectPool,他定义了对象池的应该实现的行为。
- addObject方法:往池中添加一个对象。池子里的所有对象都是通过这个方法进来的。
- borrowObject方法:从池中借走到一个对象。借走不等于删除。对象一直都属于池子,只是状态的变化。
- returnObject方法:把对象归还给对象池。归还不等于添加。对象一直都属于池子,只是状态的变化。
- invalidateObject:销毁一个对象。这个方法才会将对象从池子中删除,当然这其中最重要的就是释放对象本身持有的各种资源。
- getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。
- getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。
- clear:清理对象池。注意是清理不是清空,改方法要求的是,清理所有空闲对象,释放相关资源。
- close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。
在commons-pool2中,ObjectPool的核心实现类是GenericObjectPool。
在前面的文章中我已经解析过addObject和borrowObject方法的实现,对应链接地址:
- addObject方法解析:https://blog.csdn.net/weixin_42340670/article/details/107749058
- borrowObject方法解析:https://blog.csdn.net/weixin_42340670/article/details/106876879
本文接着解析returnObject方法在GenericObjectPool中的实现。
在讨论具体实现之前,我们还有必要看一下该方法在ObjectPool接口的具体定义是如何描述的。
ObjectPool接口中returnObject解析
/**
* Return an instance to the pool. By contract, <code>obj</code>
* <strong>must</strong> have been obtained using {@link #borrowObject()} or
* a related method as defined in an implementation or sub-interface.
*
* @param obj a {@link #borrowObject borrowed} instance to be returned.
*
* @throws IllegalStateException
* if an attempt is made to return an object to the pool that
* is in any state other than allocated (i.e. borrowed).
* Attempting to return an object more than once or attempting
* to return an object that was never borrowed from the pool
* will trigger this exception.
*
* @throws Exception if an instance cannot be returned to the pool
*/
/**
* 把一个实例(对象)归还给对象池。
* 按照约定,obj也就是要归还的对象一定是通过borrowObject方法、
* 或者是实现类或者子接口中的和borrowObject有一定逻辑关系的方法获得的(返回的)的对象。
*
* obj 通过 borrowObject借走的,现在要归还的对象。
*
* 如果要归还的对象的状态不是allocated状态、或者要归还的对象是一个从来没有被借走的对象。
* 那么就会触发IllegalStateException异常,标识非法状态。
*
* 如果是其他情况导致的不能归还,抛出Exception异常。
*/
void returnObject(T obj) throws Exception;
接口的注释,往往对于逻辑实现都给出了一些明确的指示和要求,比如说:
- returnObject和borrowObject应该成对的互斥操作。
- 所谓的borrowObject不是从池子里真的移除。
- 所谓的returnObject也不是真的往池子里添加。
- 对象一直都在大池子里,是用状态来区分是被借走了、还是已经归还了(空闲了)
接下来我们再详细的看一下GenericObjectPool对于returnObject的具体实现,是否满足注释中的说法或者要求。
GenericObjectPool类中returnObject解析
public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
/*
* All of the objects currently associated with this pool in any state. It
* excludes objects that have been destroyed. The size of
* {@link #allObjects} will always be less than or equal to {@link
* #_maxActive}. Map keys are pooled objects, values are the PooledObject
* wrappers used internally by the pool.
*/
/*
* allObjects 是一个ConcurrentHashMap,里面存储的就是对象池中所有的对象,ConcurrentHashMap能够保证并发读写安全。
* 这里面不包括已经被销毁的对象,也就是说被销毁的对象会从allObjects中被移除掉。allObjects的数量应该小于等于maxActive(最大活跃对象数量)的值。
* ConcurrentHashMap的key是IdentityWrapper类型的对象,他可以包装任何对象T,在这里他包装的T的实例就是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
* ConcurrentHashMap的value是PooledObject类型的对象,他持有的T的实例也是业务上产生的最直接的对象(数据库连接对象、线程对象等)。
* 关于IdentityWrapper和PooledObject这两个类(接口)的作用,我们在之前的《addObject方法解析中》有详细解释。
* addObject方法解析链接:https://blog.csdn.net/weixin_42340670/article/details/107749058
*/
private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
new ConcurrentHashMap<IdentityWrapper<T>, PooledObject<T>>();
/*
* 用一个链表结构的阻塞队列来存储空闲对象
*/
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
/*
* 对象工厂,GenericObjectPool的实现依赖了PooledObjectFactory来生产对象
*/
private final PooledObjectFactory<T> factory;
/**
* {@inheritDoc}
* <p>
* If {@link #getMaxIdle() maxIdle} is set to a positive value and the
* number of idle instances has reached this value, the returning instance
* is destroyed.
* <p>
* If {@link #getTestOnReturn() testOnReturn} == true, the returning
* instance is validated before being returned to the idle instance pool. In
* this case, if validation fails, the instance is destroyed.
* <p>
* Exceptions encountered destroying objects for any reason are swallowed
* but notified via a {@link SwallowedExceptionListener}.
*/
/**
* 虽然这个方法的作用是把一个不再使用的对象交还给对象池。(对象一直在池子里,所谓的还,就是变更对象的状态而已)
* 但是,如果maxIdle(最大空闲数量)设置为一个有效的正数值,并且对象池里空闲的对象数量已经达到这个数值了,那么这个要还给对象池的对象可以直接被销毁。
* 可以正常返还到对象池的情况下,如果testOnReturn设置为true,那么这个要交还的对象在重新入池之前需要校验有效性,如果校验失败,则直接销毁。
* 在销毁对象时产生的任何异常都会被吞下,并交给SwallowedExceptionListener去处理。
*/
@Override
public void returnObject(T obj) {
// 对象池allObjects是一个Map,他需要一个IdentityWrapper类型的key来获取出包装了原始对象的PooledObject对象。
// IdentityWrapper的核心作用,是为了消除原始对象(业务对象)T的可能重写的hashcode和equals方法带来的影响。
PooledObject<T> p = allObjects.get(new IdentityWrapper<T>(obj));
if (p == null) { // 如果p为空,说明这个要还的对象,已经不在池子中了。
// 不在池子中了,说明有可能被回收了,一个被使用的对象,是如何被回收的呢?
// 虽然对象状态一直是被使用,如果超过一定时间,也是可能被强制回收的。这就取决于abandonedConfig是如何配置的了。
// 如果没有配置abandonedConfig,说明就不是被强制回收的,那可能出了什么其他问题,说不清道不明的话,就抛个异常吧。
if (!isAbandonedConfig()) {
throw new IllegalStateException(
"Returned object not currently part of this pool");
} else { // 如果配置了abandonedConfig,那说明应该就是被强制回收了,也算是在计划范围内,所以直接return。
return; // Object was abandoned and removed
}
// 关于abandonedConfig希望能够参考我的另一篇解析,详细了解。
// abandonedConfig解析:https://blog.csdn.net/weixin_42340670/article/details/107136994
}
// 如果p不为空,这里加上了同步锁,保证内部逻辑处理的原子性。因为涉及到最重要的状态判断和变更。
synchronized(p) {
final PooledObjectState state = p.getState(); // 首先获取对象状态
// 要归还的对象状态就一定应该是ALLOCATED,标识使用中,如果状态不是ALLOCATED,就说明状态不符合预期,抛出个异常。
if (state != PooledObjectState.ALLOCATED) {
throw new IllegalStateException(
"Object has already been returned to this pool or is invalid");
} else { // 如果状态符合预期
// 调用PooledObject对象的markReturning方法进行对象状态变更
// PooledObject的默认实现类DefaultPooledObject的该方法只有一行代码:
// state = PooledObjectState.RETURNING; //就是改变一下状态。
p.markReturning();
}
}
long activeTime = p.getActiveTimeMillis(); // 获取对象被使用了多长时间
if (getTestOnReturn()) { // 如果testOnReturn配置为true,说明还需要校验下有效性
/*
调用PooledObjectFactory工厂方法validateObject进行校验对象的有效性,校验失败则返回false
这个方法,不同的factory有不同的实现方式
1、大多数据库连接池(比如dbcp的工厂实现PoolableConnectionFactory),默认会发一个select 1语句,校验连接的有效性。
2、jedis的工厂实现类是JedisFactory,对于该方法的实现是发一条redis的ping命令来校验连接的有效性。
有的工厂实现捕捉到异常,会直接抛出;有的实现捕捉到异常会忽略,但是最终还是会返回false。
*/
if (!factory.validateObject(p)) {
try {
destroy(p); // 如果校验不通过,则销毁该对象
} catch (Exception e) {
// 如果销毁产生异常,调用swallowException方法,交给SwallowedExceptionListener去处理(如果存在的话)
swallowException(e);
}
try {
// 如果当前正存在着调用方正在等待获取空闲对象,那么就需要创建一个空闲对象。
ensureIdle(1, false); // 这个方法下面会单独解析
} catch (Exception e) {
swallowException(e); // 如果创建对象产生异常,也一样交给SwallowedExceptionListener处理
}
// 这个方法主要用来更新一些统计信息:对象池的归还操作一共执行了多少次,最近100个被归还对象的使用时长
updateStatsReturn(activeTime);
return;
}
}
// 如果能够执行到这里,说明该对象是有效的
try {
/*
passivateObject方法按照PooledObjectFactory接口中对其职责的描述应该叫做:对一个对象进行反初始化,什么意思?
一个对象从使用中变为空闲,这个时候他本身除了持有很多核心资源外,可能还挂载了其他一些附加轻量的资源或者属性。
对象池的目的就是为了避免重复的创建这种持有昂贵资源的对象,所以即使是对象状态变为空闲,但是本身的核心资源也不能被释放。
但是那些外围的轻量的资源和属性是可以被卸载和重置的。所以可以这么理解,passivateObject方法是在对象变为空闲时,重置或者卸载那些非核心的资源和属性的。
那怎么区分核心和非核心呢? 这个事情是使用方要操心的事情,你如果觉得你的对象持有的都是核心资源,passivateObject你还必须实现,所以你可以再方法内部啥逻辑都不写。其实JedisFactory就是这么干的。
和passivateObject对应的是activateObject方法,activateObject意思是一个对象在被调用方使用前重新初始化一些附加信息。
JedisFactory对于activateObject提供了实现,内部逻辑做了redis dbindex的修正动作。
*/
factory.passivateObject(p);
} catch (Exception e1) { // 如果反初始化异常,和上面的解析近乎相同的套路:异常甩锅 + 销毁对象 + 创建空闲对象(基于判定情况) + 更新统计信息
swallowException(e1); // 异常甩锅
try {
destroy(p); // 销毁对象
} catch (Exception e) {
swallowException(e); // 异常甩锅
}
try {
ensureIdle(1, false); // 创建空闲对象(基于判定情况)
} catch (Exception e) {
swallowException(e); // 异常甩锅
}
updateStatsReturn(activeTime); // 更新统计信息
return;
}
// 如果能执行到这里,说明反初始化(轻量级资源卸载)也成功了。
/*
deallocate 意思是 de-allocate.
再回顾一下上面的逻辑,能走到这里,说明p的当前状态为:RETURNING
调用deallocate目的是把状态更改为:IDLE
在<RETURNING>和<IDLE>状态过渡的中间,执行了validate、passivate等校验和清理动作。
如果deallocate返回false,说明状态变更失败,抛出个异常:说明一下对象可能已经是空闲或者失效状态了,这两种状态不支持变更为空闲状态
*/
if (!p.deallocate()) {
throw new IllegalStateException(
"Object has already been returned to this pool or is invalid");
}
// 能执行到这里,说明状态也成功变为IDLE了
int maxIdleSave = getMaxIdle(); // 获取对象池配置的最大空闲对象数量
// 这个判断的意思就是,一个正常状态的对象池,目前空闲对象数量已经达到规定的最大值(正数)了,也就没必要再加多余的空闲了,可以直接把还回来的销毁掉。
if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p); // 销毁
} catch (Exception e) {
swallowException(e); // 异常甩锅
}
} else { // 如果对象可以被正常归还,那么把对象添加到空闲队列
if (getLifo()) { // 获取对象池的优先级配置
idleObjects.addFirst(p); // 如果是后进先出,那么把空闲对象添加到队列开头
} else {
idleObjects.addLast(p); // 如果是先进先出,那么把空闲对象添加到队列末尾
}
if (isClosed()) { // 判断一下对象池状态,如果是关闭状态,那么调用clear方法,清空对象池
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
updateStatsReturn(activeTime); // 更新统计信息
}
}
在上面的方法解析中,提到了如果被归还的对象基于某些原因(有效性校验失败、资源卸载失败)被销毁了,那么需要检查确保是否需要创建空闲对象来满足对象池的要求。当时调用了ensureIdle(1, false)方法,接下来我们就针对ensureIdle方法来做详细的源码解析。
GenericObjectPool类中ensureIdle方法解析
/**
* Tries to ensure that {@code idleCount} idle instances exist in the pool.
* <p>
* Creates and adds idle instances until either {@link #getNumIdle()} reaches {@code idleCount}
* or the total number of objects (idle, checked out, or being created) reaches
* {@link #getMaxTotal()}. If {@code always} is false, no instances are created unless
* there are threads waiting to check out instances from the pool.
*
* @param idleCount the number of idle instances desired
* @param always true means create instances even if the pool has no threads waiting
* @throws Exception if the factory's makeObject throws
*
* 尝试确保对象池中有足够的空闲对象
* 创建新的然后添加到对象池中,直到空闲对象数量达到指定的idleCount。
* 当然前提也是对象池中所有对象的数量不能大于对象池的maxTotal配置。
* idleCount参数:指定要创建多少个空闲对象
* always参数:如果为true,意味着不会关注到底有没有调用方在等待获取对象。如果为false,意味着只有存在着等待获取对象的调用方的时候才会创建对象。
* ensureIdle(1, false) 就意思着:如果当前有调用方在排队等待获取空闲对象,那么我就创建1个空闲对象。
*/
private void ensureIdle(int idleCount, boolean always) throws Exception {
// 如果idleCount小于1,或者对象池已经关闭,或者always为false但是没有处于等待的调用方。 这不会创建对象。
if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
return;
}
while (idleObjects.size() < idleCount) { // 如果空闲对象数量小于指定的idleCount
PooledObject<T> p = create(); // 创建一个新对象,“关于create方法可以参见:addObject方法的解析”
if (p == null) { // 如果p为空,说明创建失败,跳出循环
// Can't create objects, no reason to think another call to
// create will work. Give up.
break;
}
if (getLifo()) { // 判断回收策略,然后决定是往队列的开头加还是末尾加
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
}
if (isClosed()) { // 如果对象池已经处于关闭状态
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear(); // 清理所有空闲对象,包括刚刚创建的对象,防止内存泄漏
}
}