本文主要剖析 Apache Commons Pool 的“空闲对象的驱逐检测机制”的实现原理。
以下面3个步骤来循序渐进地深入剖析其实现原理:
- 启动“空闲对象的驱逐者线程”(startEvictor(...))的2个入口
- 在启动时,创建一个新的"驱逐者线程"(Evictor),并使用"驱逐者定时器"(EvictionTimer)进行调度
- 进入真正地"空闲池对象"的驱逐检测操作(evict())
下图是“空闲对象的驱逐检测机制”处理流程的时序图(阅读代码时结合着看可以加深理解):
GenericObjectPool.evict() 处理流程的时序图:
GenericObjectPool.ensureMinIdle()处理流程的时序图:
一、启动“空闲对象的驱逐者线程”(startEvictor(...))共有2个入口
1. GenericObjectPool 构造方法
GenericObjectPool(...):初始化"池对象工厂",设置"对象池配置",并启动"驱逐者线程"。
/** * 使用特定的配置来创建一个新的"通用对象池"实例。 * * @param factory The object factory to be used to create object instances * used by this pool (用于创建池对象实例的对象工厂) * @param config The configuration to use for this pool instance. (用于该对象池实例的配置信息) * The configuration is used by value. Subsequent changes to * the configuration object will not be reflected in the * pool. (随后对配置对象的更改将不会反映到池中) */ 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; this.setConfig(config); // 启动"驱逐者线程" startEvictor(this.getTimeBetweenEvictionRunsMillis()); }
2. BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(...) - 设置"驱逐者线程"的运行间隔时间
可以动态地更新"驱逐者线程"的运行调度间隔时间。
/** * 设置"空闲对象的驱逐者线程"的运行调度间隔时间。(同时,会立即启动"驱逐者线程") * <p> * 如果该值是非正数,则没有"空闲对象的驱逐者线程"将运行。 * <p> * 默认是 {@code -1},即没有"空闲对象的驱逐者线程"在后台运行着。 * <p> * 上一层入口:{@link GenericObjectPool#setConfig(GenericObjectPoolConfig)}<br> * 顶层入口:{@link GenericObjectPool#GenericObjectPool(PooledObjectFactory, GenericObjectPoolConfig)}, * 在最后还会调用{@link #startEvictor(long)}来再次启动"空闲对象的驱逐者线程"。<br> * 这样在初始化时,这里创建的"驱逐者线程"就多余了,会立刻被销毁掉。<br> * 但这里为什么要这样实现呢?<br> * 我的理解是:为了能动态地更新"驱逐者线程"的调度间隔时间。 * * @param timeBetweenEvictionRunsMillis * number of milliseconds to sleep between evictor runs ("驱逐者线程"运行的间隔毫秒数) * * @see #getTimeBetweenEvictionRunsMillis */ public final void setTimeBetweenEvictionRunsMillis( long timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; // 启动"驱逐者线程" this.startEvictor(timeBetweenEvictionRunsMillis); }
二、startEvictor(long delay) - 启动“空闲对象的驱逐者线程”
如果有一个"驱逐者线程"(Evictor)运行着,则会先停止它;
然后创建一个新的"驱逐者线程",并使用"驱逐者定时器"(EvictionTimer)进行调度。
// 空闲对象的驱逐回收策略 /** 用于初始化"驱逐者线程"的同步对象 */ final Object evictionLock = new Object(); /** 空闲对象驱逐者线程 */ private Evictor evictor = null; // @GuardedBy("evictionLock") /** 驱逐检测对象迭代器 */ Iterator<PooledObject<T>> evictionIterator = null; // @GuardedBy("evictionLock") /** * 启动"空闲对象的驱逐者线程"。 * <p> * 如果有一个"驱逐者线程"({@link Evictor})运行着,则会先停止它; * 然后创建一个新的"驱逐者线程",并使用"驱逐者定时器"({@link EvictionTimer})进行调度。 * * <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 void startEvictor(long delay) { synchronized (evictionLock) { // 同步锁 if (null != evictor) { // 先释放申请的资源 EvictionTimer.cancel(evictor); evictor = null; evictionIterator = null; } if (delay > 0) { evictor = new Evictor(); EvictionTimer.schedule(evictor, delay, delay); } } }
2.1 Evictor - "驱逐者线程"实现
Evictor,"空闲对象的驱逐者"定时任务,继承自 TimerTask。TimerTask 是一个可由定时器(Timer)调度执行一次或重复执行的任务。
核心实现逻辑:
1. evict():执行numTests个空闲池对象的驱逐测试,驱逐那些符合驱逐条件的被检测对象;
2. ensureMinIdle():试图确保配置的对象池中可用"空闲池对象"实例的最小数量。
/** * Class loader for evictor thread to use since in a J2EE or similar * environment the context class loader for the evictor thread may have * visibility of the correct factory. See POOL-161. * 驱逐者线程的类加载器 */ private final ClassLoader factoryClassLoader; // Inner classes /** * "空闲对象的驱逐者"定时任务,继承自{@link TimerTask}。 * * @see GenericObjectPool#GenericObjectPool(PooledObjectFactory, GenericObjectPoolConfig) * @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis(long) */ class Evictor extends TimerTask { /** * 运行对象池维护线程。 * 驱逐对象具有驱逐者的资格,同时保证空闲实例可用的最小数量。 * 因为调用"驱逐者线程"的定时器是被所有对象池共享的, * 但对象池可能存在不同的类加载器中,所以驱逐者必须确保采取的任何行为 * 都得在与对象池相关的工厂的类加载器下。 */ @Override public void run() { ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader(); try { // Set the class loader for the factory (设置"工厂的类加载器") Thread.currentThread().setContextClassLoader( factoryClassLoader); // Evict from the pool (从"对象池"中驱逐) try { // 1. 执行numTests个空闲池对象的驱逐测试,驱逐那些符合驱逐条件的被检测对象 evict(); // 抽象方法 } catch(Exception e) { swallowException(e); } catch(OutOfMemoryError oome) { // Log problem but give evictor thread a chance to continue // in case error is recoverable oome.printStackTrace(System.err); } // Re-create idle instances. (重新创建"空闲池对象"实例) try { // 2. 试图确保配置的对象池中可用"空闲池对象"实例的最小数量 ensureMinIdle(); // 抽象方法 } catch (Exception e) { swallowException(e); } } finally { // Restore the previous CCL Thread.currentThread().setContextClassLoader(savedClassLoader); } } }
2.2 EvictionTimer - "驱逐者定时器"实现
EvictionTimer,提供一个所有"对象池"共享的"空闲对象的驱逐定时器"。此类包装标准的定时器(Timer),并追踪有多少个"对象池"使用它。
核心实现逻辑:
schedule(TimerTask task, long delay, long period):添加指定的驱逐任务到这个定时器
/** * 提供一个所有"对象池"共享的"空闲对象的驱逐定时器"。 * * 此类包装标准的定时器({@link Timer}),并追踪有多少个对象池使用它。 * * 如果没有对象池使用这个定时器,它会被取消。这样可以防止线程一直运行着 * (这会导致内存泄漏),防止应用程序关闭或重新加载。 * <p> * 此类是包范围的,以防止其被纳入到池框架的公共API中。 * <p> * <font color="red">此类是线程安全的!</font> * * @since 2.0 */ class EvictionTimer { /** Timer instance (定时器实例) */ private static Timer _timer; //@GuardedBy("this") /** Static usage count tracker (使用计数追踪器) */ private static int _usageCount; //@GuardedBy("this") /** Prevent instantiation (防止实例化) */ private EvictionTimer() { // Hide the default constructor } /** * 添加指定的驱逐任务到这个定时器。 * 任务,通过调用该方法添加的,必须调用{@link #cancel(TimerTask)}来取消这个任务, * 以防止内存或消除泄漏。 * * @param task Task to be scheduled (定时调度的任务) * @param delay Delay in milliseconds before task is executed (任务执行前的等待时间) * @param period Time in milliseconds between executions (执行间隔时间) */ static synchronized void schedule(TimerTask task, long delay, long period) { if (null == _timer) { // Force the new Timer thread to be created with a context class // loader set to the class loader that loaded this library ClassLoader ccl = AccessController.doPrivileged( new PrivilegedGetTccl()); try { AccessController.doPrivileged(new PrivilegedSetTccl( EvictionTimer.class.getClassLoader())); _timer = new Timer("commons-pool-EvictionTimer", true); } finally { AccessController.doPrivileged(new PrivilegedSetTccl(ccl)); } } // 增加"使用计数器",并调度"任务" _usageCount++; _timer.schedule(task, delay, period); } /** * 从定时器中删除指定的驱逐者任务。 * <p> * Remove the specified eviction task from the timer. * * @param task Task to be scheduled (定时调度任务) */ static synchronized void cancel(TimerTask task) { task.cancel(); // 1. 将任务的状态标记为"取消(CANCELLED)"状态 _usageCount--; if (_usageCount == 0) { // 2. 如果没有对象池使用这个定时器,定时器就会被取消 _timer.cancel(); _timer = null; } } /** * {@link PrivilegedAction} used to get the ContextClassLoader (获取"上下文类加载器") */ private static class PrivilegedGetTccl implements PrivilegedAction<ClassLoader> { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } } /** * {@link PrivilegedAction} used to set the ContextClassLoader (设置"上下文类加载器") */ private static class PrivilegedSetTccl implements PrivilegedAction<Void> { /** ClassLoader */ private final ClassLoader cl; /** * Create a new PrivilegedSetTccl using the given classloader * @param cl ClassLoader to use */ PrivilegedSetTccl(ClassLoader cl) { this.cl = cl; } @Override public Void run() { Thread.currentThread().setContextClassLoader(cl); return null; } } }
三、"驱逐者线程"和"驱逐者定时器"都准备就绪,现在真正地开始"空闲池对象"的驱逐检测操作(evict())
BaseGenericObjectPool.evict():驱逐检测操作的抽象声明
/** * 执行{@link numTests}个空闲池对象的驱逐测试,驱逐那些符合驱逐条件的被检测对象。 * <p> * 如果{@code testWhileIdle}为{@code true},则被检测的对象在访问期间是有效的(无效则会被删除); * 否则,仅有那些池对象的空闲时间超过{@code minEvicableIdleTimeMillis}会被删除。 * * @throws Exception when there is a problem evicting idle objects. (当这是一个有问题的驱逐空闲池对象时,才会抛出Exception异常。) */ public abstract void evict() throws Exception;
GenericObjectPool.evict():"通用对象池"的驱逐检测操作实现
核心实现逻辑:
1. 确保"对象池"还打开着
2. 获取"驱逐回收策略"
3. 获取"驱逐配置"
4. 对所有待检测的"空闲对象"进行驱逐检测
4.1 初始化"驱逐检测对象(空闲池对象)的迭代器"
4.2 将"池对象"标记为"开始驱逐状态"
4.3 进行真正的"驱逐检测"操作(EvictionPolicy.evict(...))
4.3.1 如果"池对象"是可驱逐的,则销毁它
4.3.2 否则,是否允许空闲时进行有效性测试
4.3.2.1 先激活"池对象"
4.3.2.2 使用PooledObjectFactory.validateObject(PooledObject)进行"池对象"的有效性校验
4.3.2.2.1 如果"池对象"不是有效的,则销毁它
4.3.2.2.2 否则,还原"池对象"状态
4.3.2.3 通知"空闲对象队列",驱逐测试已经结束
5. 是否要移除"被废弃的池对象"
/** 池的空闲池对象列表 */ private final LinkedBlockingDeque<PooledObject<T>> idleObjects = new LinkedBlockingDeque<PooledObject<T>>(); /** 池对象工厂 */ private final PooledObjectFactory<T> factory; // 空闲对象的驱逐回收策略 /** 用于初始化"驱逐者线程"的同步对象 */ final Object evictionLock = new Object(); /** 空闲对象驱逐者线程 */ private Evictor evictor = null; // @GuardedBy("evictionLock") /** 驱逐检测对象("空闲池对象")的迭代器 */ Iterator<PooledObject<T>> evictionIterator = null; // @GuardedBy("evictionLock") /** 被废弃的池对象追踪的配置属性 */ private volatile AbandonedConfig abandonedConfig = null; /** * {@inheritDoc} * <p> * 按顺序对被审查的对象进行连续驱逐检测,对象是以"从最老到最年轻"的顺序循环。 */ @Override public void evict() throws Exception { // 1. 确保"对象池"还打开着 this.assertOpen(); if (idleObjects.size() > 0) { PooledObject<T> underTest = null; // 测试中的池对象 // 2. 获取"驱逐回收策略" EvictionPolicy<T> evictionPolicy = this.getEvictionPolicy(); synchronized (evictionLock) { // 驱逐锁定 // 3. 获取"驱逐配置" EvictionConfig evictionConfig = new EvictionConfig( this.getMinEvictableIdleTimeMillis(), this.getSoftMinEvictableIdleTimeMillis(), this.getMinIdle() ); // 4. 对所有待检测的"空闲对象"进行驱逐检测 for (int i = 0, m = this.getNumTests(); i < m; i++) { // 4.1 初始化"驱逐检测对象(空闲池对象)的迭代器" if (evictionIterator == null || !evictionIterator.hasNext()) { // 已对所有空闲对象完成一次遍历 // 根据"对象池使用行为"赋值驱逐迭代器 if (this.getLifo()) { evictionIterator = idleObjects.descendingIterator(); } else { evictionIterator = idleObjects.iterator(); } } 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; i--; evictionIterator = null; continue; } // 4.2 将"池对象"标记为"开始驱逐状态" if (!underTest.startEvictionTest()) { // Object was borrowed in another thread // Don't count this as an eviction test so reduce i; i--; continue; } boolean testWhileIdle = this.getTestWhileIdle(); // 是否要在对象空闲时测试有效性 // 4.3 进行真正的"驱逐检测"操作(EvictionPolicy.evict(...)) if (evictionPolicy.evict(evictionConfig, underTest, idleObjects.size())) { // 4.3.1 如果"池对象"是可驱逐的,则销毁它 this.destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { // 4.3.2 否则,是否允许空闲时进行有效性测试 if (testWhileIdle) { // 允许空闲时进行有效性测试 // 4.3.2.1 先激活"池对象" boolean active = false; try { factory.activateObject(underTest); active = true; } catch (Exception e) { this.destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } // 4.3.2.2 使用PooledObjectFactory.validateObject(PooledObject)进行"池对象"的有效性校验 if (active) { if (!factory.validateObject(underTest)) { // 4.3.2.2.1 如果"池对象"不是有效的,则销毁它 this.destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } else { try { // 4.3.2.2.2 否则,还原"池对象"状态 factory.passivateObject(underTest); } catch (Exception e) { this.destroy(underTest); destroyedByEvictorCount.incrementAndGet(); } } } } // 4.3.2.3 通知"空闲对象队列",驱逐测试已经结束 if (!underTest.endEvictionTest(idleObjects)) { // TODO - May need to add code here once additional // states are used } } } } } // 5. 是否要移除"被废弃的池对象" AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnMaintenance()) { this.removeAbandoned(ac); } }
BaseGenericObjectPool.ensureMinIdle():"确保对象池中可用"空闲池对象"实例的最小数量"的抽象声明
/** * 试图确保配置的对象池中可用"空闲池对象"实例的最小数量。 * * @throws Exception if an error occurs creating idle instances */ abstract void ensureMinIdle() throws Exception;
GenericObjectPool.ensureMinIdle():"确保对象池中可用"空闲池对象"实例的最小数量"实现
@Override void ensureMinIdle() throws Exception { this.ensureIdle(this.getMinIdle(), true); } /** * 返回对象池中维护的空闲对象的最小数量目标。 * <p> * 此设置仅会在{@link #getTimeBetweenEvictionRunsMillis()}的返回值大于0时, * 且该值是正整数时才会生效。 * <p> * 默认是 {@code 0},即对象池不维护空闲的池对象。 * * @return The minimum number of objects. (空闲对象的最小数量) * * @see #setMinIdle(int) * @see #setMaxIdle(int) * @see #setTimeBetweenEvictionRunsMillis(long) */ @Override public int getMinIdle() { int maxIdleSave = this.getMaxIdle(); if (this.minIdle > maxIdleSave) { return maxIdleSave; } else { return minIdle; } } /** * 试图确保对象池中存在的{@code idleCount}个空闲实例。 * <p> * 创建并添加空闲实例,直到空闲实例数量({@link #getNumIdle()})达到{@code idleCount}个, * 或者池对象的总数(空闲、检出、被创建)达到{@link #getMaxTotal()}。 * 如果{@code always}是false,则不会创建实例,除非线程在等待对象池中的实例检出。 * * @param idleCount the number of idle instances desired (期望的空闲实例数量) * @param always true means create instances even if the pool has no threads waiting * (true意味着即使对象池没有线程等待,也会创建实例) * @throws Exception if the factory's makeObject throws */ private void ensureIdle(int idleCount, boolean always) throws Exception { if (idleCount < 1 || this.isClosed() || (!always && !idleObjects.hasTakeWaiters())) { return; } while (idleObjects.size() < idleCount) { PooledObject<T> p = this.create(); if (p == null) { // Can't create objects (不能创建对象), no reason to think another call to // create will work. Give up. break; } // "新的池对象"可以立刻被使用 if (this.getLifo()) { // LIFO(后进先出) idleObjects.addFirst(p); } else { // FIFO(先进先出) idleObjects.addLast(p); } } } /** * 尝试着创建一个新的包装的池对象。 * * @return The new wrapped pooled object * * @throws Exception if the object factory's {@code makeObject} fails */ private PooledObject<T> create() throws Exception { // 1. 对象池是否被耗尽判断 int localMaxTotal = getMaxTotal(); long newCreateCount = createCount.incrementAndGet(); if (localMaxTotal > -1 && newCreateCount > localMaxTotal || newCreateCount > Integer.MAX_VALUE) { createCount.decrementAndGet(); return null; // 没有池对象可创建 } final PooledObject<T> p; try { // 2. 使用PooledObjectFactory.makeObject()来制造一个新的池对象 p = factory.makeObject(); } catch (Exception e) { createCount.decrementAndGet(); throw e; } AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getLogAbandoned()) { p.setLogAbandoned(true); } createdCount.incrementAndGet(); // 3. 将新创建的池对象追加到"池的所有对象映射表"中 allObjects.put(p.getObject(), p); return p; }
3.1 "驱逐回收策略"实现
EvictionConfig:"驱逐回收策略"配置信息
/** * 此类用于将对象池的配置信息传递给"驱逐回收策略({@link EvictionPolicy})"实例。 * <p> * <font color="red">此类是不可变的,且是线程安全的。</font> * * @since 2.0 */ public class EvictionConfig { // final 字段修饰保证其不可变性 /** 池对象的最大空闲驱逐时间(当池对象的空闲时间超过该值时,立马被强制驱逐掉) */ private final long idleEvictTime; /** 池对象的最小空闲驱逐时间(当池对象的空闲时间超过该值时,被纳入驱逐对象列表里) */ private final long idleSoftEvictTime; /** 对象池的最小空闲池对象数量 */ private final int minIdle; /** * 创建一个新的"驱逐回收策略"配置实例。 * <p> * <font color="red">实例是不可变的。</font> * * @param poolIdleEvictTime Expected to be provided by (池对象的最大空闲驱逐时间(ms)) * {@link BaseGenericObjectPool#getMinEvictableIdleTimeMillis()} * @param poolIdleSoftEvictTime Expected to be provided by (池对象的最小空闲驱逐时间(ms)) * {@link BaseGenericObjectPool#getSoftMinEvictableIdleTimeMillis()} * @param minIdle Expected to be provided by (对象池的最小空闲池对象数量) * {@link GenericObjectPool#getMinIdle()} or * {@link GenericKeyedObjectPool#getMinIdlePerKey()} */ 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; } /** * 获取"池对象的最大空闲驱逐时间(ms)"。 * <p> * 当池对象的空闲时间超过该值时,立马被强制驱逐掉。 * <p> * How the evictor behaves based on this value will be determined by the * configured {@link EvictionPolicy}. * * @return The {@code idleEvictTime} in milliseconds */ public long getIdleEvictTime() { return idleEvictTime; } /** * 获取"池对象的最小空闲驱逐时间(ms)"。 * <p> * 当池对象的空闲时间超过该值时,被纳入驱逐对象列表里。 * <p> * How the evictor behaves based on this value will be determined by the * configured {@link EvictionPolicy}. * * @return The (@code idleSoftEvictTime} in milliseconds */ public long getIdleSoftEvictTime() { return idleSoftEvictTime; } /** * 获取"对象池的最小空闲池对象数量"。 * <p> * How the evictor behaves based on this value will be determined by the * configured {@link EvictionPolicy}. * * @return The {@code minIdle} */ public int getMinIdle() { return minIdle; } }
EvictionPolicy<T>:"驱逐回收策略"声明
/** * 为了提供对象池的一个自定义"驱逐回收策略", * 使用者必须提供该接口的一个实现(如{@link DefaultEvictionPolicy})。 * * @param <T> the type of objects in the pool (对象池中对象的类型) * * @since 2.0 */ public interface EvictionPolicy<T> { /** * 一个对象池中的空闲对象是否应该被驱逐,调用此方法来测试。(驱逐行为声明) * * @param config The pool configuration settings related to eviction (与驱逐相关的对象池配置设置) * @param underTest The pooled object being tested for eviction (正在被驱逐测试的池对象) * @param idleCount The current number of idle objects in the pool including * the object under test (当前对象池中的空闲对象数,包括测试中的对象) * @return <code>true</code> if the object should be evicted, otherwise * <code>false</code> (如果池对象应该被驱逐掉,就返回{@code true};否则,返回{@code false}。) */ boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount); }
DefaultEvictionPolicy<T>:提供用在对象池的"驱逐回收策略"的默认实现,继承自EvictionPolicy<T>
/** * 提供用在对象池的"驱逐回收策略"的默认实现,继承自{@link EvictionPolicy}。 * <p> * 如果满足以下条件,对象将被驱逐: * <ul> * <li>池对象的空闲时间超过{@link GenericObjectPool#getMinEvictableIdleTimeMillis()} * <li>对象池中的空闲对象数超过{@link GenericObjectPool#getMinIdle()},且池对象的空闲时间超过{@link GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} * </ul> * <font color="red">此类是不可变的,且是线程安全的。</font> * * @param <T> the type of objects in the pool (对象池中对象的类型) * * @since 2.0 */ public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> { /** * 如果对象池中的空闲对象是否应该被驱逐,调用此方法来测试。(驱逐行为实现) */ @Override public boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount) { if ((idleCount > config.getMinIdle() && underTest.getIdleTimeMillis() > config.getIdleSoftEvictTime()) || underTest.getIdleTimeMillis() > config.getIdleEvictTime()) { return true; } return false; } }