在之前的文章中分析过GenericObjectPool的构造方法(参见:https://blog.csdn.net/weixin_42340670/article/details/107300577),在构造方法内部逻辑的最后调用了startEvictor方法。这个方法的作用是在构造完对象池后,启动回收器来监控回收空闲对象。startEvictor定义在GenericObjectPool的父类BaseGenericObjectPool(抽象)类中,我们先看一下这个方法的源码
BaseGenericObjectPool.startEvictor方法解析
final Object evictionLock = new Object(); // 用来和synchronized配合使用,实现多线程同步
private Evictor evictor = null; // @GuardedBy("evictionLock") 回收器对象
EvictionIterator evictionIterator = null; // @GuardedBy("evictionLock") 回收迭代器。里面存储的都是空闲对象
/**
* <p>Starts the evictor with the given delay. If there is an evictor
* running when this method is called, it is stopped and replaced with a
* new evictor with the specified delay.</p>
*
* <p>This method needs to be final, since it is called from a constructor.
* See POOL-195.</p>
*
* @param delay time in milliseconds before start and between eviction runs
*
* 用给定延迟时间来开启回收器。
* 如果已经存在着一个在运行的回收器了,则停掉它,再根据给定的延迟时间重新创建一个回收器。
* 这个方法需要被标记为final,因为他会被构造方法调用。
* 为什么被构造方法调用就要设置为final?注释里说去官网参阅 POOL-195 ISSUE。
* (https://issues.apache.org/jira/browse/POOL-195)
*/
final void startEvictor(long delay) {
synchronized (evictionLock) { // 同步处理
if (null != evictor) { // 如果evictor不为空,说明已经存在回收器了
EvictionTimer.cancel(evictor); // 取消掉当前的回收器
evictor = null; // 回收器对象设置为空,便于垃圾回收。
evictionIterator = null; // 回收器迭代器设置为空,便于垃圾回收。
}
/*
这个判断很重要。如果传入的delay是个小于0的数值。那里面的逻辑就不会执行。但是上面的逻辑都已经执行完了。
所以调用startEvictor时,如果传入delay小于0:
如果当前回收器不为空,当前的回收器被清除。
如果当前回收器为空,那么就等于啥都没干。
只有delay大于0时,才会创建一个新的回收器,赋值给当前的回收器对象
*/
if (delay > 0) {
evictor = new Evictor(); // 创建一个新的回收器
// 延迟delay毫秒后,开始执行回收任务,之后每隔delay毫秒后重复执行。
EvictionTimer.schedule(evictor, delay, delay);
}
}
}
实际上在GenericObjectPool中startEvictor方法一共被三个地方调用
- 构造方法:创建了对象池之后,则尝试开启回收线程
- close方法:关闭对象池的时候,也会调用startEvictor用来清理回收对象池中的资源。
- setTimeBetweenEvictionRunsMillis方法:该方法的作用是设置回收线程的回收周期(多少毫秒执行一轮回收)。既然设置了回收周期,那么就意味着一定开启回收线程,所以顺带着就调用了startEvictor来启动回收线程。
在startEvictor方法中,核心逻辑就是创建Evictor,并启动调度,接下来我们再看下Evictor类的源码,Evictor类是BaseGenericObjectPool的内部类,以下摘取了BaseGenericObjectPool类中和Evictor相关的代码进行解析
BaseGenericObjectPool.Evictor类解析
public abstract class BaseGenericObjectPool<T> {
/*
* ---可以先看内部类Evictor的解析,再回头来看这个属性的解析----
* Class loader for evictor thread to use since, in a JavaEE or similar
* environment, the context class loader for the evictor thread may not have
* visibility of the correct factory. See POOL-161. Uses a weak reference to
* avoid potential memory leaks if the Pool is discarded rather than closed.
* 用于回收器线程的类加载器。
* 在JavaEE或类似的环境中,回收器上下文类加载器可能对于对象工厂没有可见性
* 多个回收器共用一个Timer,Timer的加载器取决于最先实例化的对象池的加载器。
* 如果池被丢弃而不是关闭,使用弱引用可以避免潜在的内存泄漏。
* 更详细的解释可以参考:https://issues.apache.org/jira/browse/POOL-161
*/
private final WeakReference<ClassLoader> factoryClassLoader;
public abstract void evict() throws Exception; // 定义的抽象方法,需要子类实现,完成空闲对象的清除
abstract void ensureMinIdle() throws Exception; // 定义的抽象方法,需要子类实现,完成空闲对象的最低数量保障
/**
* The idle object evictor {@link TimerTask}.
*
* @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis
*/
class Evictor extends TimerTask { // 继承了抽象类TimerTask,决定了他可以放到定时调度中,同时也不需实现run方法
/**
* Run pool maintenance. Evict objects qualifying for eviction and then
* ensure that the minimum number of idle instances are available.
* Since the Timer that invokes Evictors is shared for all Pools but
* pools may exist in different class loaders, the Evictor ensures that
* any actions taken are under the class loader of the factory
* associated with the pool.
* run方法干的什么事情呢?
* 运行对象池的维护任务。回收那些符合被回收条件的对象,确保有最小数量的空闲对象可用就可以。
* 因为调用回收器的定时器是所有对象池共享的,但是不同的对象池可能是被不同的classloader加载的。不同的classloader之间类信息不共享。
* 回收器的很多操作还是最终要调用对象池的相关方法来完成的,所以回收器必须保证他的回收线程的上下文classloader要和对象池的classloader一致。
*/
@Override
public void run() {
// 首先获取当前线程执行上下文的classloader,这个就是Timer类的classloader。
ClassLoader savedClassLoader =
Thread.currentThread().getContextClassLoader();
try {
// 关于这个factoryClassLoader是在BaseGenericObjectPool的构造方法中赋值的。(参考:https://blog.csdn.net/weixin_42340670/article/details/107300577)
// 如果factoryClassLoader为空,说明对象池的类加载器是根加载器
if (factoryClassLoader != null) { // 如果factoryClassLoader不为空,说明对象池的加载器很可能是AppClassLoader或者某个框架自定义的加载器
// Set the class loader for the factory
ClassLoader cl = factoryClassLoader.get(); // 因为factoryClassLoader本身是个弱引用,里面包的是真正的classloader,所以调用get方法获取到真正的classloader
if (cl == null) {
/*
通过之前文章中对BaseGenericObjectPool的构造方法解析,我们能够知道,只要factoryClassLoader不为空,那么里面就一定存放了当时的classloader。
但是如果当前的这个方法里get出来的为空,那么是因为factoryClassLoader是一个WeakReference,WeakReference弱引用所持有的对象,在发生GC时就会触发回收,但是他持有的classloader是否能被回收,还取决于这个classloader是否还存在强引用、是否还存在着被他加载的还使用着的类以及类的对象。所以如果cl为空,那只能说明这个classloader关联的【所有类的对象、类】已经都不被引用了、都已经被回收了(这就说明对象池已经被回收了),然后classloader本身才可以被回收。那么既然对象池都不存在了,所以回收任务也可以取消掉了。
*/
// The pool has been dereferenced and the class loader
// GC'd. Cancel this timer so the pool can be GC'd as
// well.
cancel(); // 这个cancel方法是TimerTask提供的方法,用来将任务状态置为CANCELLED,就不会再被Timer调度了。
return;
}
// 能走到这一步说明,正常获取到了对象池的当时的classloader,所以将当前上下文的classloader设置为cl
Thread.currentThread().setContextClassLoader(cl);
}
// Evict from the pool
try {
/*
执行清除动作
这个方法定义在BaseGenericObjectPool类中,是个抽象方法,需要子类去实现
源码:public abstract void evict() throws Exception;
*/
evict();
} catch(Exception e) {
swallowException(e); // 吞下异常,自己尝试消化(可以自己实现SwallowedExceptionListener接口,设置为异常监听处理程序,如果没定义,那这方法就等于啥也没干)
} catch(OutOfMemoryError oome) {
// Log problem but give evictor thread a chance to continue
// in case error is recoverable
// 如果发生内存错误,捕捉到打印日志。不向外抛出,
// 因为OutOfMemoryError是Error类型异常,这种异常一旦发生,不捕捉到,那么jvm将会终止程序。
// 所以这里catch到,是为了防止万一是偶发的,可恢复的,那么保证程序不被终止。
oome.printStackTrace(System.err);
}
// Re-create idle instances.
try {
/*
确保对象池里有足够的(min idle配置的数值)空闲对象
这个方法定义在BaseGenericObjectPool类中,是个抽象方法,需要子类去实现
源码:abstract void ensureMinIdle() throws Exception;
*/
ensureMinIdle(); //
} catch (Exception e) {
swallowException(e); // 吞下异常,自己尝试消化(可以自己实现SwallowedExceptionListener接口,设置为异常监听处理程序,如果没定义,那这方法就等于啥也没干)
}
} finally {
// Restore the previous CCL 最后还是要把线程上下文的classloader重置为Timer类的classloader
Thread.currentThread().setContextClassLoader(savedClassLoader);
}
}
}
final void swallowException(Exception e) {
// 只有了setSwallowedExceptionListener了,getSwallowedExceptionListener才能get出来
// setSwallowedExceptionListener方法需要传入一个SwallowedExceptionListener
SwallowedExceptionListener listener = getSwallowedExceptionListener();
if (listener == null) { // 如果设置了异常监听
return;
}
try {
listener.onSwallowException(e); // 调用异常监听器的处理方法
} catch (OutOfMemoryError oome) {
throw oome;
} catch (VirtualMachineError vme) {
throw vme;
} catch (Throwable t) {
// Ignore. Enjoy the irony.
}
}
}
在Evictor类的run方法中,调用了两个非常核心的方法,但是这两个方法都是声明在BaseGenericObjectPool类中的抽象方法,需要子类实现
- evict方法
- ensureMinIdle方法
GenericObjectPool是对象池的一个核心实现类,他是一定会实现这两个方法的,我们先来看evict方法在GenericObjectPool中的实现
GenericObjectPool.evict方法解析
public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
final Object evictionLock = new Object(); // 对象锁。startEvictor和evict方法都用这把锁。
// 回收迭代器,内部持有的就是空闲对象列表。
// 为什么不用jdk自带的迭代器呢?抽象出EvictionIterator的最重要意义是,对外屏蔽了是正向迭代还是反向迭代。(取决于lifo配置)
EvictionIterator evictionIterator = null;
/**
* {@inheritDoc}
* <p>
* Successive activations of this method examine objects in sequence,
* cycling through objects in oldest-to-youngest order.
*/
@Override
public void evict() throws Exception {
assertOpen(); // 校验对象池的状态,不能是close状态,如果是close状态,会抛出"Pool not open"异常
if (idleObjects.size() > 0) { // 如果空闲对象数量大于0
PooledObject<T> underTest = null;
// 获取回收策略对象,回收策略也是支持用户自定义,框架提供的默认的是DefaultEvictionPolicy
EvictionPolicy<T> evictionPolicy = getEvictionPolicy();
synchronized (evictionLock) { // 同步处理
/*
根据对象池的几个参数创建一个回收配置对象
minEvictableIdleTimeMillis:一个毫秒数值,用来指定超过这个空闲时间的会被回收。如果是负数,配置不生效。
softMinEvictableIdleTimeMillis:一个毫秒数值,用来指定在空闲对象数量超过minIdle设置,且某个空闲对象超过这个空闲时间的才可以会被回收。
minIdle:对象池里要保留的最小空间对象数量。
*/
EvictionConfig evictionConfig = new EvictionConfig(
getMinEvictableIdleTimeMillis(),
getSoftMinEvictableIdleTimeMillis(),
getMinIdle());
// 获取testWhileIdle配置,如果为true,说明需要校验一下对象的有效性。
boolean testWhileIdle = getTestWhileIdle();
/*
getNumTests这个函数比较重要,下面有单独解析。
返回的是需要回收的对象数量。
*/
for (int i = 0, m = getNumTests(); i < m; i++) {
// 如果迭代器为null,或者已经遍历到头了,则重新new一个
if (evictionIterator == null || !evictionIterator.hasNext()) {
evictionIterator = new EvictionIterator(idleObjects);
}
if (!evictionIterator.hasNext()) { // 如果没后可遍历的元素了,说明连接池满了,没有空闲对象
// Pool exhausted, nothing to do here
return; // 没有可回收的,直接退出。
}
try {
underTest = evictionIterator.next(); // 获取到待处理的空闲对象
} catch (NoSuchElementException nsee) {
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
// 在遍历过程中,虽然hasNext可能是true,但是某个对象可能又重新被其他线程使用,等next时候可能就获取失败
i--; // 既然都没获取到,i--之后,就可以再重新加一轮检测
evictionIterator = null; // next已经获取不到了,也迭代到头了,设置为null,等下一轮循环的时候,就会重新创建一个
continue; // 进行下一轮循环
}
// 走到这里说明,获取到了一个要处理的空闲对象
/*
startEvictionTest是检查并更改对象的状态,IDLE -> EVICTION
所有和状态变更相关的方法都是synchronized的,包括后面介绍的endEvictionTest
*/
if (!underTest.startEvictionTest()) {
// 走到这里,说明状态变更失败,很可能就是因为,又重新被使用了,状态不是空闲了。
// Object was borrowed in another thread
// Don't count this as an eviction test so reduce i;
i--; // 既然变为非空闲了,i--之后,就可以再重新加一轮检测
continue; // 进行下一轮循环
}
// User provided eviction policy could throw all sorts of
// crazy exceptions. Protect against such an exception
// killing the eviction thread.
/*
用户提供的回收策略可能会抛出各种疯狂的异常,所以这里去catch异常的老祖宗Throwable
以防止异常导致回收线程被终止,致使处理流程不完整。
*/
boolean evict; // 定义个变量,标识获取到的空闲对象underTest到底可不可以被回收
try {
// 这里调用回收策略的evict方法来判定underTest是否可以被回收
// 上面讲过框架提供的默认回收策略是DefaultEvictionPolicy,下面会对DefaultEvictionPolicy的evict方法进行单独解析
evict = evictionPolicy.evict(evictionConfig, underTest,
idleObjects.size());
} catch (Throwable t) {
// Slightly convoluted as SwallowedExceptionListener
// uses Exception rather than Throwable
PoolUtils.checkRethrow(t); // 先处理两个SwallowedExceptionListener不关注的异常
swallowException(new Exception(t)); // 再通过SwallowedExceptionListener处理
// Don't evict on error conditions
evict = false; // 如果捕捉到异常,认为不可以被回收
}
if (evict) { // 如果可以被回收
destroy(underTest); // 调用destroy方法销毁underTest
destroyedByEvictorCount.incrementAndGet(); // 回收器销毁计数器加1
} else { // 如果不能被回收
if (testWhileIdle) { // 如果testWhileIdle为ture,虽然不能被回收,但是也检查下对象是否可用(检查下资源是不是还在,连接是不是断开了等)
boolean active = false; // 定义个变量,标识空闲对象是否还存活,初始化为false,
try {
// 调用activateObject激活该空闲对象,本质上不是为了激活,而是通过这个方法可以判定是否还存活,这一步里面可能会有一些资源的开辟行为。
factory.activateObject(underTest);
active = true; // 设置为true
} catch (Exception e) { // 如果激活的时候,发生了异常,就说明该空闲对象已经失联了。
destroy(underTest); // 调用destroy方法销毁underTest
destroyedByEvictorCount.incrementAndGet(); // 回收器销毁计数器加1
}
if (active) { // 如果激活成功,说明联系到了,但是能不能干活,还得再校验一下
if (!factory.validateObject(underTest)) { // 再通过进行validateObject校验有效性
// 如果校验失败,说明对象已经不可用了
destroy(underTest); // 调用destroy方法销毁underTest
destroyedByEvictorCount.incrementAndGet(); // 回收器销毁计数器加1
} else {
/*
如果校验通过,说明对象还是可用的,但是我们的回收线程的重点任务是回收和校验空闲对象,因为校验还激活了空闲对象,分配了额外的资源
既然都校验完了,那么就通过passivateObject把在activateObject中开辟的资源释放掉。
备注:passivateObject 这个方法是一个比较理想态。其实很少有下游框架实现这个方法。
*/
try {
factory.passivateObject(underTest);
} catch (Exception e) { // 如果passivateObject失败,也可以说明underTest这个空闲对象不可用了
destroy(underTest); // 调用destroy方法销毁underTest
destroyedByEvictorCount.incrementAndGet(); // 回收器销毁计数器加1
}
}
}
}
/*
既然当前空闲对象不能被回收
那么需要重置空闲对象的状态 ,因为之前通过调用startEvictionTest之后,对象状态已经改变:IDLE -> EVICTION
调用endEvictionTest可以把状态还原:EVICTION -> IDLE
*/
if (!underTest.endEvictionTest(idleObjects)) {
// TODO - May need to add code here once additional
// states are used
// 如果状态重置失败了,就在这里处理。
}
}
}
}
}
// 这里是关于遗弃对象的处理。关于这部分内容希望你参考我之前写过的关于abandonedConfig解析,来对照理解。
// abandonedConfig解析链接地址:https://blog.csdn.net/weixin_42340670/article/details/107136994
AbandonedConfig ac = this.abandonedConfig; // 获取遗弃对象配置
// 如果removeAbandonedOnMaintenance配置为true,说明在空闲回收的同时也进行遗弃对象的处理
if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
removeAbandoned(ac);
}
}
/**
* Calculate the number of objects to test in a run of the idle object
* evictor.
*
* @return The number of objects to test for validity
* 返回的是回收器中每次回收任务需要检查的空闲对象的数量
*/
private int getNumTests() {
int numTestsPerEvictionRun = getNumTestsPerEvictionRun(); // 先获取的是numTestsPerEvictionRun配置的值,默认值是3
if (numTestsPerEvictionRun >= 0) { // 如果numTestsPerEvictionRun配置了一个大于等于0的值(如果配置为0,就等于不回收)
// 取numTestsPerEvictionRun和空闲对象列表长度的最小值,防止越界
return Math.min(numTestsPerEvictionRun, idleObjects.size());
} else { // 如果numTestsPerEvictionRun设置的是一个小于0的值
/*
如果现在一共有10个空闲对象,numTestsPerEvictionRun配置为-3
则返回的就是 Math.ceil(10/-3的绝对值) = Math.ceil(10/3) = Math.ceil(3.3) = 4
如果配置的-1,返回的就是10,也就是回收所有的空闲对象
*/
return (int) (Math.ceil(idleObjects.size() /
Math.abs((double) numTestsPerEvictionRun)));
}
}
}
GenericObjectPool.ensureMinIdle方法解析
public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
implements ObjectPool<T>, GenericObjectPoolMXBean, UsageTracking<T> {
// 略去了很多代码
@Override
void ensureMinIdle() throws Exception {
ensureIdle(getMinIdle(), true);
}
/**
* 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,意味着只有存在着等待获取对象的调用方的时候才会创建对象。
*/
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(); // 创建一个新对象
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(); // 清理所有
}
}
}
DefaultEvictionPolicy.evict方法解析
public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
@Override
public boolean evict(EvictionConfig config, PooledObject<T> underTest,
int idleCount) {
/*
A || B,是短路运算的,A为ture,则B不会执行。B是兜底方案。
A:(config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() && config.getMinIdle() < idleCount)
config.getIdleSoftEvictTime():获取的就是softMinEvictableIdleTimeMillis配置
underTest.getIdleTimeMillis():获取的就是对象的空闲时间
config.getMinIdle():获取的是minIdle配置,需要保留的最小空闲数量
idleCount:所有空闲对象的数量
B:config.getIdleEvictTime() < underTest.getIdleTimeMillis()
config.getIdleEvictTime():获取的就是minEvictableIdleTimeMillis配置
underTest.getIdleTimeMillis():获取的就是对象的空闲时间
通过这段代码,我们就可以得出为什么了非要搞一个softMinEvictableIdleTimeMillis出来,他的作用是什么?
softMinEvictableIdleTimeMillis可以设置的比minEvictableIdleTimeMillis小,他在检查时,除了检查空闲对象的空闲时间,还会校验空闲对象的数量,如果空闲对象数量已经小于等于minIdle,就算超过了他设置的时间,他的判断也为false,所以继续进行后面的判断。
而一旦空闲时间超过了minEvictableIdleTimeMillis,那么就不会再保留了。
所以softMinEvictableIdleTimeMillis意味着在空闲了一个相对较短的时间的情况下,有些空闲对象可以不回收,用来维持空闲对象的最低保障。这应该就是soft要表达的意思,他并非完全根据时间来强制判断。
而一旦某个对象空闲时间超过了minEvictableIdleTimeMillis了,就强制回收了。
所以如果要设置softMinEvictableIdleTimeMillis,一定要保证小于minEvictableIdleTimeMillis,否则就没意义了。
*/
if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
config.getMinIdle() < idleCount) ||
config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
return true;
}
return false;
}
}