You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/09/02 15:11:18 UTC

[tomcat] 01/02: Update internal fork of Commons Pool to 2.11.1

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit fbd85f4cfce632c1e667a295b067dde35a9975b2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Sep 2 12:27:20 2021 +0100

    Update internal fork of Commons Pool to 2.11.1
---
 MERGE.txt                                          |    2 +-
 .../dbcp/pool2/BaseKeyedPooledObjectFactory.java   |   70 +-
 .../apache/tomcat/dbcp/pool2/BaseObjectPool.java   |   84 +-
 .../tomcat/dbcp/pool2/BasePooledObjectFactory.java |   53 +-
 java/org/apache/tomcat/dbcp/pool2/DestroyMode.java |   10 +-
 .../apache/tomcat/dbcp/pool2/KeyedObjectPool.java  |   23 +-
 .../dbcp/pool2/KeyedPooledObjectFactory.java       |   58 +-
 java/org/apache/tomcat/dbcp/pool2/ObjectPool.java  |   14 +-
 java/org/apache/tomcat/dbcp/pool2/PoolUtils.java   | 2360 ++++++++++----------
 .../org/apache/tomcat/dbcp/pool2/PooledObject.java |  308 ++-
 .../tomcat/dbcp/pool2/PooledObjectFactory.java     |   48 +-
 .../tomcat/dbcp/pool2/PooledObjectState.java       |   28 +-
 .../dbcp/pool2/SwallowedExceptionListener.java     |    2 +-
 java/org/apache/tomcat/dbcp/pool2/TrackedUse.java  |   30 +-
 .../apache/tomcat/dbcp/pool2/UsageTracking.java    |   15 +-
 .../tomcat/dbcp/pool2/impl/AbandonedConfig.java    |  325 +--
 .../dbcp/pool2/impl/BaseGenericObjectPool.java     | 2279 +++++++++++--------
 .../dbcp/pool2/impl/BaseObjectPoolConfig.java      |  906 +++++---
 .../apache/tomcat/dbcp/pool2/impl/CallStack.java   |   24 +-
 .../tomcat/dbcp/pool2/impl/CallStackUtils.java     |    2 +-
 .../dbcp/pool2/impl/DefaultEvictionPolicy.java     |   33 +-
 .../dbcp/pool2/impl/DefaultPooledObject.java       |  321 +--
 .../dbcp/pool2/impl/DefaultPooledObjectInfo.java   |   38 +-
 .../pool2/impl/DefaultPooledObjectInfoMBean.java   |   47 +-
 .../tomcat/dbcp/pool2/impl/EvictionConfig.java     |  139 +-
 .../tomcat/dbcp/pool2/impl/EvictionTimer.java      |  206 +-
 .../dbcp/pool2/impl/GenericKeyedObjectPool.java    | 1910 ++++++++--------
 .../pool2/impl/GenericKeyedObjectPoolConfig.java   |  102 +-
 .../pool2/impl/GenericKeyedObjectPoolMXBean.java   |  217 +-
 .../tomcat/dbcp/pool2/impl/GenericObjectPool.java  | 1197 +++++-----
 .../dbcp/pool2/impl/GenericObjectPoolConfig.java   |   72 +-
 .../dbcp/pool2/impl/GenericObjectPoolMXBean.java   |  206 +-
 .../pool2/impl/InterruptibleReentrantLock.java     |   10 +-
 .../dbcp/pool2/impl/LinkedBlockingDeque.java       | 1671 +++++++-------
 .../tomcat/dbcp/pool2/impl/NoOpCallStack.java      |    8 +-
 .../tomcat/dbcp/pool2/impl/PoolImplUtils.java      |  125 +-
 .../dbcp/pool2/impl/PooledSoftReference.java       |   39 +-
 .../dbcp/pool2/impl/SecurityManagerCallStack.java  |  104 +-
 .../dbcp/pool2/impl/SoftReferenceObjectPool.java   |  396 ++--
 .../tomcat/dbcp/pool2/impl/ThrowableCallStack.java |   41 +-
 webapps/docs/changelog.xml                         |    4 +
 41 files changed, 7465 insertions(+), 6062 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 73ecd41..4682268 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -62,7 +62,7 @@ Pool2
 Sub-tree
 src/main/java/org/apache/commons/pool2
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-a38c52529beb04bf0815b7d95fb4a393ea110dee
+rel/commons-pool-2.11.1 (2021-08-17)
 
 DBCP2
 Sub-tree
diff --git a/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java b/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java
index b0f0c34..a9877f1 100644
--- a/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java
+++ b/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java
@@ -36,6 +36,21 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> extends BaseObject
         implements KeyedPooledObjectFactory<K, V> {
 
     /**
+     * Reinitialize an instance to be returned by the pool.
+     * <p>
+     * The default implementation is a no-op.
+     * </p>
+     *
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be activated
+     */
+    @Override
+    public void activateObject(final K key, final PooledObject<V> p)
+        throws Exception {
+        // The default implementation is a no-op.
+    }
+
+    /**
      * Create an instance that can be served by the pool.
      *
      * @param key the key used when constructing the object
@@ -48,14 +63,19 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> extends BaseObject
         throws Exception;
 
     /**
-     * Wrap the provided instance with an implementation of
-     * {@link PooledObject}.
-     *
-     * @param value the instance to wrap
+     * Destroy an instance no longer needed by the pool.
+     * <p>
+     * The default implementation is a no-op.
+     * </p>
      *
-     * @return The provided instance, wrapped by a {@link PooledObject}
+     * @param key the key used when selecting the instance
+     * @param p a {@code PooledObject} wrapping the instance to be destroyed
      */
-    public abstract PooledObject<V> wrap(V value);
+    @Override
+    public void destroyObject(final K key, final PooledObject<V> p)
+        throws Exception {
+        // The default implementation is a no-op.
+    }
 
     @Override
     public PooledObject<V> makeObject(final K key) throws Exception {
@@ -63,16 +83,16 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> extends BaseObject
     }
 
     /**
-     * Destroy an instance no longer needed by the pool.
+     * Uninitialize an instance to be returned to the idle object pool.
      * <p>
      * The default implementation is a no-op.
      * </p>
      *
-     * @param key the key used when selecting the instance
-     * @param p a {@code PooledObject} wrapping the instance to be destroyed
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be passivated
      */
     @Override
-    public void destroyObject(final K key, final PooledObject<V> p)
+    public void passivateObject(final K key, final PooledObject<V> p)
         throws Exception {
         // The default implementation is a no-op.
     }
@@ -93,32 +113,12 @@ public abstract class BaseKeyedPooledObjectFactory<K, V> extends BaseObject
     }
 
     /**
-     * Reinitialize an instance to be returned by the pool.
-     * <p>
-     * The default implementation is a no-op.
-     * </p>
+     * Wrap the provided instance with an implementation of
+     * {@link PooledObject}.
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be activated
-     */
-    @Override
-    public void activateObject(final K key, final PooledObject<V> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
-
-    /**
-     * Uninitialize an instance to be returned to the idle object pool.
-     * <p>
-     * The default implementation is a no-op.
-     * </p>
+     * @param value the instance to wrap
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be passivated
+     * @return The provided instance, wrapped by a {@link PooledObject}
      */
-    @Override
-    public void passivateObject(final K key, final PooledObject<V> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
+    public abstract PooledObject<V> wrap(V value);
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java b/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java
index d17b494..d8c931c 100644
--- a/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java
+++ b/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java
@@ -30,55 +30,45 @@ package org.apache.tomcat.dbcp.pool2;
  */
 public abstract class BaseObjectPool<T> extends BaseObject implements ObjectPool<T> {
 
-    @Override
-    public abstract T borrowObject() throws Exception;
-
-    @Override
-    public abstract void returnObject(T obj) throws Exception;
-
-    @Override
-    public abstract void invalidateObject(T obj) throws Exception;
+    private volatile boolean closed;
 
     /**
-     * Not supported in this base implementation.
+     * Not supported in this base implementation. Subclasses should override
+     * this behavior.
      *
-     * @return a negative value.
+     * @throws UnsupportedOperationException if the pool does not implement this
+     *          method
      */
     @Override
-    public int getNumIdle() {
-        return -1;
+    public void addObject() throws Exception, UnsupportedOperationException {
+        throw new UnsupportedOperationException();
     }
 
     /**
-     * Not supported in this base implementation.
+     * Throws an {@code IllegalStateException} when this pool has been
+     * closed.
      *
-     * @return a negative value.
+     * @throws IllegalStateException when this pool has been closed.
+     *
+     * @see #isClosed()
      */
-    @Override
-    public int getNumActive() {
-        return -1;
+    protected final void assertOpen() throws IllegalStateException {
+        if (isClosed()) {
+            throw new IllegalStateException("Pool not open");
+        }
     }
 
-    /**
-     * Not supported in this base implementation.
-     *
-     * @throws UnsupportedOperationException if the pool does not implement this
-     *          method
-     */
     @Override
-    public void clear() throws Exception, UnsupportedOperationException {
-        throw new UnsupportedOperationException();
-    }
+    public abstract T borrowObject() throws Exception;
 
     /**
-     * Not supported in this base implementation. Subclasses should override
-     * this behavior.
+     * Not supported in this base implementation.
      *
      * @throws UnsupportedOperationException if the pool does not implement this
      *          method
      */
     @Override
-    public void addObject() throws Exception, UnsupportedOperationException {
+    public void clear() throws Exception, UnsupportedOperationException {
         throw new UnsupportedOperationException();
     }
 
@@ -95,29 +85,39 @@ public abstract class BaseObjectPool<T> extends BaseObject implements ObjectPool
     }
 
     /**
-     * Has this pool instance been closed.
+     * Not supported in this base implementation.
      *
-     * @return {@code true} when this pool has been closed.
+     * @return a negative value.
      */
-    public final boolean isClosed() {
-        return closed;
+    @Override
+    public int getNumActive() {
+        return -1;
     }
 
     /**
-     * Throws an {@code IllegalStateException} when this pool has been
-     * closed.
+     * Not supported in this base implementation.
      *
-     * @throws IllegalStateException when this pool has been closed.
+     * @return a negative value.
+     */
+    @Override
+    public int getNumIdle() {
+        return -1;
+    }
+
+    @Override
+    public abstract void invalidateObject(T obj) throws Exception;
+
+    /**
+     * Has this pool instance been closed.
      *
-     * @see #isClosed()
+     * @return {@code true} when this pool has been closed.
      */
-    protected final void assertOpen() throws IllegalStateException {
-        if (isClosed()) {
-            throw new IllegalStateException("Pool not open");
-        }
+    public final boolean isClosed() {
+        return closed;
     }
 
-    private volatile boolean closed = false;
+    @Override
+    public abstract void returnObject(T obj) throws Exception;
 
     @Override
     protected void toStringAppendFields(final StringBuilder builder) {
diff --git a/java/org/apache/tomcat/dbcp/pool2/BasePooledObjectFactory.java b/java/org/apache/tomcat/dbcp/pool2/BasePooledObjectFactory.java
index 6714324..3a309f4 100644
--- a/java/org/apache/tomcat/dbcp/pool2/BasePooledObjectFactory.java
+++ b/java/org/apache/tomcat/dbcp/pool2/BasePooledObjectFactory.java
@@ -31,6 +31,17 @@ package org.apache.tomcat.dbcp.pool2;
  * @since 2.0
  */
 public abstract class BasePooledObjectFactory<T> extends BaseObject implements PooledObjectFactory<T> {
+
+    /**
+     *  No-op.
+     *
+     *  @param p ignored
+     */
+    @Override
+    public void activateObject(final PooledObject<T> p) throws Exception {
+        // The default implementation is a no-op.
+    }
+
     /**
      * Creates an object instance, to be wrapped in a {@link PooledObject}.
      * <p>This method <strong>must</strong> support concurrent, multi-threaded
@@ -44,14 +55,15 @@ public abstract class BasePooledObjectFactory<T> extends BaseObject implements P
     public abstract T create() throws Exception;
 
     /**
-     * Wrap the provided instance with an implementation of
-     * {@link PooledObject}.
-     *
-     * @param obj the instance to wrap
+     *  No-op.
      *
-     * @return The provided instance, wrapped by a {@link PooledObject}
+     *  @param p ignored
      */
-    public abstract PooledObject<T> wrap(T obj);
+    @Override
+    public void destroyObject(final PooledObject<T> p)
+        throws Exception  {
+        // The default implementation is a no-op.
+    }
 
     @Override
     public PooledObject<T> makeObject() throws Exception {
@@ -61,16 +73,16 @@ public abstract class BasePooledObjectFactory<T> extends BaseObject implements P
     /**
      *  No-op.
      *
-     *  @param p ignored
+     * @param p ignored
      */
     @Override
-    public void destroyObject(final PooledObject<T> p)
-        throws Exception  {
+    public void passivateObject(final PooledObject<T> p)
+        throws Exception {
         // The default implementation is a no-op.
     }
 
     /**
-     * This implementation always returns {@code true}.
+     * Always returns {@code true}.
      *
      * @param p ignored
      *
@@ -82,23 +94,12 @@ public abstract class BasePooledObjectFactory<T> extends BaseObject implements P
     }
 
     /**
-     *  No-op.
+     * Wraps the provided instance with an implementation of
+     * {@link PooledObject}.
      *
-     *  @param p ignored
-     */
-    @Override
-    public void activateObject(final PooledObject<T> p) throws Exception {
-        // The default implementation is a no-op.
-    }
-
-    /**
-     *  No-op.
+     * @param obj the instance to wrap
      *
-     * @param p ignored
+     * @return The provided instance, wrapped by a {@link PooledObject}
      */
-    @Override
-    public void passivateObject(final PooledObject<T> p)
-        throws Exception {
-        // The default implementation is a no-op.
-    }
+    public abstract PooledObject<T> wrap(T obj);
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/DestroyMode.java b/java/org/apache/tomcat/dbcp/pool2/DestroyMode.java
index ed8ca47..2b8b683 100644
--- a/java/org/apache/tomcat/dbcp/pool2/DestroyMode.java
+++ b/java/org/apache/tomcat/dbcp/pool2/DestroyMode.java
@@ -17,14 +17,16 @@
 package org.apache.tomcat.dbcp.pool2;
 
 /**
- * Destroy context provided to object factories via destroyObject methods. Values provide information about why the pool
- * is asking for a pooled object to be destroyed.
+ * Destroy context provided to object factories via {@code destroyObject} and {@code invalidateObject} methods. Values
+ * provide information about why the pool is asking for a pooled object to be destroyed.
  *
  * @since 2.9.0
  */
 public enum DestroyMode {
-    /** Normal destroy */
+
+    /** Normal destroy. */
     NORMAL,
-    /** Destroy abandoned object */
+
+    /** Destroy abandoned object. */
     ABANDONED
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java b/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java
index f2712d9..86d638a 100644
--- a/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java
+++ b/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java
@@ -35,14 +35,14 @@ import java.util.NoSuchElementException;
  * <code style="color:#00C">try</code> {
  *     obj = pool.borrowObject(key);
  *     <code style="color:#0C0">//...use the object...</code>
- * } <code style="color:#00C">catch</code>(Exception e) {
+ * } <code style="color:#00C">catch</code> (Exception e) {
  *     <code style="color:#0C0">// invalidate the object</code>
  *     pool.invalidateObject(key, obj);
  *     <code style="color:#0C0">// do not return the object to the pool twice</code>
  *     obj = <code style="color:#00C">null</code>;
  * } <code style="color:#00C">finally</code> {
  *     <code style="color:#0C0">// make sure the object is returned to the pool</code>
- *     <code style="color:#00C">if</code>(<code style="color:#00C">null</code> != obj) {
+ *     <code style="color:#00C">if</code> (<code style="color:#00C">null</code> != obj) {
  *         pool.returnObject(key, obj);
  *     }
  * }</pre>
@@ -69,7 +69,7 @@ import java.util.NoSuchElementException;
 public interface KeyedObjectPool<K, V> extends Closeable {
 
     /**
-     * Create an object using the {@link KeyedPooledObjectFactory factory} or
+     * Creates an object using the {@link KeyedPooledObjectFactory factory} or
      * other implementation dependent mechanism, passivate it, and then place it
      * in the idle object pool. {@code addObject} is useful for
      * "pre-loading" a pool with idle objects (Optional operation).
@@ -136,7 +136,7 @@ public interface KeyedObjectPool<K, V> extends Closeable {
     }
 
     /**
-     * Obtains an instance from this pool for the specified {@code key}.
+     * Borrows an instance from this pool for the specified {@code key}.
      * <p>
      * Instances returned from this method will have been either newly created
      * with {@link KeyedPooledObjectFactory#makeObject makeObject} or will be
@@ -197,7 +197,7 @@ public interface KeyedObjectPool<K, V> extends Closeable {
     void clear(K key) throws Exception, UnsupportedOperationException;
 
     /**
-     * Close this pool, and free any resources associated with it.
+     * Closes this pool, and free any resources associated with it.
      * <p>
      * Calling {@link #addObject addObject} or
      * {@link #borrowObject borrowObject} after invoking this method on a pool
@@ -211,7 +211,7 @@ public interface KeyedObjectPool<K, V> extends Closeable {
     void close();
 
     /**
-     * Returns the total number of instances currently borrowed from this pool but
+     * Gets the total number of instances currently borrowed from this pool but
      * not yet returned. Returns a negative value if this information is not
      * available.
      * @return the total number of instances currently borrowed from this pool but
@@ -220,7 +220,7 @@ public interface KeyedObjectPool<K, V> extends Closeable {
     int getNumActive();
 
     /**
-     * Returns the number of instances currently borrowed from but not yet
+     * Gets the number of instances currently borrowed from but not yet
      * returned to the pool corresponding to the given {@code key}.
      * Returns a negative value if this information is not available.
      *
@@ -231,14 +231,14 @@ public interface KeyedObjectPool<K, V> extends Closeable {
     int getNumActive(K key);
 
     /**
-     * Returns the total number of instances currently idle in this pool.
+     * Gets the total number of instances currently idle in this pool.
      * Returns a negative value if this information is not available.
      * @return the total number of instances currently idle in this pool.
      */
     int getNumIdle();
 
     /**
-     * Returns the number of instances corresponding to the given
+     * Gets the number of instances corresponding to the given
      * {@code key} currently idle in this pool. Returns a negative value if
      * this information is not available.
      *
@@ -269,7 +269,6 @@ public interface KeyedObjectPool<K, V> extends Closeable {
      */
     void invalidateObject(K key, V obj) throws Exception;
 
-
     /**
      * Invalidates an object from the pool, using the provided
      * {@link DestroyMode}.
@@ -287,12 +286,12 @@ public interface KeyedObjectPool<K, V> extends Closeable {
      *
      * @param key the key used to obtain the object
      * @param obj a {@link #borrowObject borrowed} instance to be returned.
-     * @param mode destroy activation context provided to the factory
+     * @param destroyMode destroy activation context provided to the factory
      *
      * @throws Exception if the instance cannot be invalidated
      * @since 2.9.0
      */
-    default void invalidateObject(final K key, final V obj, final DestroyMode mode) throws Exception {
+    default void invalidateObject(final K key, final V obj, final DestroyMode destroyMode) throws Exception {
         invalidateObject(key, obj);
     }
 
diff --git a/java/org/apache/tomcat/dbcp/pool2/KeyedPooledObjectFactory.java b/java/org/apache/tomcat/dbcp/pool2/KeyedPooledObjectFactory.java
index 0a24a47..428155d 100644
--- a/java/org/apache/tomcat/dbcp/pool2/KeyedPooledObjectFactory.java
+++ b/java/org/apache/tomcat/dbcp/pool2/KeyedPooledObjectFactory.java
@@ -77,21 +77,20 @@ package org.apache.tomcat.dbcp.pool2;
 public interface KeyedPooledObjectFactory<K, V> {
 
     /**
-     * Create an instance that can be served by the pool and
-     * wrap it in a {@link PooledObject} to be managed by the pool.
+     * Reinitializes an instance to be returned by the pool.
      *
-     * @param key the key used when constructing the object
+     * @param key the key used when selecting the object
+     * @param p a {@code PooledObject} wrapping the instance to be activated
      *
-     * @return a {@code PooledObject} wrapping an instance that can
-     * be served by the pool.
+     * @throws Exception if there is a problem activating {@code obj},
+     *    this exception may be swallowed by the pool.
      *
-     * @throws Exception if there is a problem creating a new instance,
-     *    this will be propagated to the code requesting an object.
+     * @see #destroyObject
      */
-    PooledObject<V> makeObject(K key) throws Exception;
+    void activateObject(K key, PooledObject<V> p) throws Exception;
 
     /**
-     * Destroy an instance no longer needed by the pool.
+     * Destroys an instance no longer needed by the pool.
      * <p>
      * It is important for implementations of this method to be aware that there
      * is no guarantee about what state {@code obj} will be in and the
@@ -114,11 +113,11 @@ public interface KeyedPooledObjectFactory<K, V> {
     void destroyObject(K key, PooledObject<V> p) throws Exception;
 
     /**
-     * Destroy an instance no longer needed by the pool, using the provided {@link DestroyMode}.
+     * Destroys an instance no longer needed by the pool, using the provided {@link DestroyMode}.
      *
      * @param key the key used when selecting the instance
      * @param p a {@code PooledObject} wrapping the instance to be destroyed
-     * @param mode DestroyMode providing context to the factory
+     * @param destroyMode DestroyMode providing context to the factory
      *
      * @throws Exception should be avoided as it may be swallowed by
      *    the pool implementation.
@@ -129,45 +128,46 @@ public interface KeyedPooledObjectFactory<K, V> {
      * @see DestroyMode
      * @since 2.9.0
      */
-    default void destroyObject(final K key, final PooledObject<V> p, final DestroyMode mode) throws Exception {
+    default void destroyObject(final K key, final PooledObject<V> p, final DestroyMode destroyMode) throws Exception {
         destroyObject(key, p);
     }
 
     /**
-     * Ensures that the instance is safe to be returned by the pool.
+     * Creates an instance that can be served by the pool and
+     * wrap it in a {@link PooledObject} to be managed by the pool.
      *
-     * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be validated
+     * @param key the key used when constructing the object
      *
-     * @return {@code false} if {@code obj} is not valid and should
-     *         be dropped from the pool, {@code true} otherwise.
+     * @return a {@code PooledObject} wrapping an instance that can
+     * be served by the pool.
+     *
+     * @throws Exception if there is a problem creating a new instance,
+     *    this will be propagated to the code requesting an object.
      */
-    boolean validateObject(K key, PooledObject<V> p);
+    PooledObject<V> makeObject(K key) throws Exception;
 
     /**
-     * Reinitialize an instance to be returned by the pool.
+     * Uninitializes an instance to be returned to the idle object pool.
      *
      * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be activated
+     * @param p a {@code PooledObject} wrapping the instance to be passivated
      *
-     * @throws Exception if there is a problem activating {@code obj},
+     * @throws Exception if there is a problem passivating {@code obj},
      *    this exception may be swallowed by the pool.
      *
      * @see #destroyObject
      */
-    void activateObject(K key, PooledObject<V> p) throws Exception;
+    void passivateObject(K key, PooledObject<V> p) throws Exception;
 
     /**
-     * Uninitialize an instance to be returned to the idle object pool.
+     * Ensures that the instance is safe to be returned by the pool.
      *
      * @param key the key used when selecting the object
-     * @param p a {@code PooledObject} wrapping the instance to be passivated
-     *
-     * @throws Exception if there is a problem passivating {@code obj},
-     *    this exception may be swallowed by the pool.
+     * @param p a {@code PooledObject} wrapping the instance to be validated
      *
-     * @see #destroyObject
+     * @return {@code false} if {@code obj} is not valid and should
+     *         be dropped from the pool, {@code true} otherwise.
      */
-    void passivateObject(K key, PooledObject<V> p) throws Exception;
+    boolean validateObject(K key, PooledObject<V> p);
 }
 
diff --git a/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java b/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java
index 426bc96..656b29f 100644
--- a/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java
+++ b/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java
@@ -31,14 +31,14 @@ import java.util.NoSuchElementException;
  *     obj = pool.borrowObject();
  *     <code style="color:#00C">try</code> {
  *         <code style="color:#0C0">//...use the object...</code>
- *     } <code style="color:#00C">catch</code>(Exception e) {
+ *     } <code style="color:#00C">catch</code> (Exception e) {
  *         <code style="color:#0C0">// invalidate the object</code>
  *         pool.invalidateObject(obj);
  *         <code style="color:#0C0">// do not return the object to the pool twice</code>
  *         obj = <code style="color:#00C">null</code>;
  *     } <code style="color:#00C">finally</code> {
  *         <code style="color:#0C0">// make sure the object is returned to the pool</code>
- *         <code style="color:#00C">if</code>(<code style="color:#00C">null</code> != obj) {
+ *         <code style="color:#00C">if</code> (<code style="color:#00C">null</code> != obj) {
  *             pool.returnObject(obj);
  *        }
  *     }
@@ -92,7 +92,7 @@ public interface ObjectPool<T> extends Closeable {
     }
 
     /**
-     * Obtains an instance from this pool.
+     * Borrows an instance from this pool.
      * <p>
      * Instances returned from this method will have been either newly created
      * with {@link PooledObjectFactory#makeObject} or will be a previously
@@ -151,14 +151,14 @@ public interface ObjectPool<T> extends Closeable {
     void close();
 
     /**
-     * Returns the number of instances currently borrowed from this pool. Returns
+     * Gets the number of instances currently borrowed from this pool. Returns
      * a negative value if this information is not available.
      * @return the number of instances currently borrowed from this pool.
      */
     int getNumActive();
 
     /**
-     * Returns the number of instances currently idle in this pool. This may be
+     * Gets the number of instances currently idle in this pool. This may be
      * considered an approximation of the number of objects that can be
      * {@link #borrowObject borrowed} without creating any new instances.
      * Returns a negative value if this information is not available.
@@ -198,12 +198,12 @@ public interface ObjectPool<T> extends Closeable {
      * </p>
      *
      * @param obj a {@link #borrowObject borrowed} instance to be disposed.
-     * @param mode destroy activation context provided to the factory
+     * @param destroyMode destroy activation context provided to the factory
      *
      * @throws Exception if the instance cannot be invalidated
      * @since 2.9.0
      */
-    default void invalidateObject(final T obj, final DestroyMode mode) throws Exception {
+    default void invalidateObject(final T obj, final DestroyMode destroyMode) throws Exception {
         invalidateObject(obj);
     }
 
diff --git a/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java b/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java
index 28e9531..d2d304d 100644
--- a/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java
+++ b/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java
@@ -35,595 +35,274 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
  */
 public final class PoolUtils {
 
-    private static final String MSG_FACTOR_NEGATIVE = "factor must be positive.";
-    private static final String MSG_MIN_IDLE = "minIdle must be non-negative.";
-    static final String MSG_NULL_KEY = "key must not be null.";
-    private static final String MSG_NULL_KEYED_POOL = "keyedPool must not be null.";
-    static final String MSG_NULL_KEYS = "keys must not be null.";
-    private static final String MSG_NULL_POOL = "pool must not be null.";
-
     /**
-     * Timer used to periodically check pools idle object count. Because a
-     * {@link Timer} creates a {@link Thread}, an IODH is used.
+     * Encapsulate the logic for when the next poolable object should be
+     * discarded. Each time update is called, the next time to shrink is
+     * recomputed, based on the float factor, number of idle instances in the
+     * pool and high water mark. Float factor is assumed to be between 0 and 1.
+     * Values closer to 1 cause less frequent erosion events. Erosion event
+     * timing also depends on numIdle. When this value is relatively high (close
+     * to previously established high water mark), erosion occurs more
+     * frequently.
      */
-    static class TimerHolder {
-        static final Timer MIN_IDLE_TIMER = new Timer(true);
-    }
+    private static final class ErodingFactor {
+        /** Determines frequency of "erosion" events */
+        private final float factor;
 
-    /**
-     * PoolUtils instances should NOT be constructed in standard programming.
-     * Instead, the class should be used procedurally: PoolUtils.adapt(aPool);.
-     * This constructor is public to permit tools that require a JavaBean
-     * instance to operate.
-     */
-    public PoolUtils() {
-    }
+        /** Time of next shrink event */
+        private transient volatile long nextShrinkMillis;
 
-    /**
-     * Should the supplied Throwable be re-thrown (eg if it is an instance of
-     * one of the Throwables that should never be swallowed). Used by the pool
-     * error handling for operations that throw exceptions that normally need to
-     * be ignored.
-     *
-     * @param t
-     *            The Throwable to check
-     * @throws ThreadDeath
-     *             if that is passed in
-     * @throws VirtualMachineError
-     *             if that is passed in
-     */
-    public static void checkRethrow(final Throwable t) {
-        if (t instanceof ThreadDeath) {
-            throw (ThreadDeath) t;
+        /** High water mark - largest numIdle encountered */
+        private transient volatile int idleHighWaterMark;
+
+        /**
+         * Creates a new ErodingFactor with the given erosion factor.
+         *
+         * @param factor
+         *            erosion factor
+         */
+        public ErodingFactor(final float factor) {
+            this.factor = factor;
+            nextShrinkMillis = System.currentTimeMillis() + (long) (900000 * factor); // now + 15 min * factor
+            idleHighWaterMark = 1;
         }
-        if (t instanceof VirtualMachineError) {
-            throw (VirtualMachineError) t;
+
+        /**
+         * Gets the time of the next erosion event.
+         *
+         * @return next shrink time
+         */
+        public long getNextShrink() {
+            return nextShrinkMillis;
         }
-        // All other instances of Throwable will be silently swallowed
-    }
 
-    /**
-     * Periodically check the idle object count for the pool. At most one idle
-     * object will be added per period. If there is an exception when calling
-     * {@link ObjectPool#addObject()} then no more checks will be performed.
-     *
-     * @param pool
-     *            the pool to check periodically.
-     * @param minIdle
-     *            if the {@link ObjectPool#getNumIdle()} is less than this then
-     *            add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a pool,
-     *            see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <T> the type of objects in the pool
-     * @return the {@link TimerTask} that will periodically check the pools idle
-     *         object count.
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null} or when {@code minIdle} is
-     *             negative or when {@code period} isn't valid for
-     *             {@link Timer#schedule(TimerTask, long, long)}
-     */
-    public static <T> TimerTask checkMinIdle(final ObjectPool<T> pool,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return "ErodingFactor{" + "factor=" + factor +
+                    ", idleHighWaterMark=" + idleHighWaterMark + '}';
         }
-        if (minIdle < 0) {
-            throw new IllegalArgumentException(MSG_MIN_IDLE);
+
+        /**
+         * Updates internal state using the supplied time and numIdle.
+         *
+         * @param nowMillis
+         *            current time
+         * @param numIdle
+         *            number of idle elements in the pool
+         */
+        public void update(final long nowMillis, final int numIdle) {
+            final int idle = Math.max(0, numIdle);
+            idleHighWaterMark = Math.max(idle, idleHighWaterMark);
+            final float maxInterval = 15f;
+            final float minutes = maxInterval +
+                    ((1f - maxInterval) / idleHighWaterMark) * idle;
+            nextShrinkMillis = nowMillis + (long) (minutes * 60000f * factor);
         }
-        final TimerTask task = new ObjectPoolMinIdleTimerTask<>(pool, minIdle);
-        getMinIdleTimer().schedule(task, 0L, period);
-        return task;
     }
-
     /**
-     * Periodically check the idle object count for the key in the keyedPool. At
-     * most one idle object will be added per period. If there is an exception
-     * when calling {@link KeyedObjectPool#addObject(Object)} then no more
-     * checks for that key will be performed.
+     * Decorates a keyed object pool, adding "eroding" behavior. Based on the
+     * configured erosion factor, objects returning to the pool
+     * may be invalidated instead of being added to idle capacity.
      *
-     * @param keyedPool
-     *            the keyedPool to check periodically.
-     * @param key
-     *            the key to check the idle count of.
-     * @param minIdle
-     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less than
-     *            this then add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a
-     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return the {@link TimerTask} that will periodically check the pools idle
-     *         object count.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code key} is {@code null} or
-     *             when {@code minIdle} is negative or when {@code period} isn't
-     *             valid for {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> object pool key type
+     * @param <V> object pool value type
      */
-    public static <K, V> TimerTask checkMinIdle(
-            final KeyedObjectPool<K, V> keyedPool, final K key,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+    private static class ErodingKeyedObjectPool<K, V> implements
+            KeyedObjectPool<K, V> {
+
+        /** Underlying pool */
+        private final KeyedObjectPool<K, V> keyedPool;
+
+        /** Erosion factor */
+        private final ErodingFactor erodingFactor;
+
+        /**
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying pool - must not be null
+         * @param erodingFactor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #erodingFactor
+         */
+        protected ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
+                final ErodingFactor erodingFactor) {
+            if (keyedPool == null) {
+                throw new IllegalArgumentException(
+                        MSG_NULL_KEYED_POOL);
+            }
+            this.keyedPool = keyedPool;
+            this.erodingFactor = erodingFactor;
         }
-        if (key == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEY);
+
+        /**
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying pool
+         * @param factor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #erodingFactor
+         */
+        public ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
+                final float factor) {
+            this(keyedPool, new ErodingFactor(factor));
         }
-        if (minIdle < 0) {
-            throw new IllegalArgumentException(MSG_MIN_IDLE);
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void addObject(final K key) throws Exception,
+                IllegalStateException, UnsupportedOperationException {
+            keyedPool.addObject(key);
         }
-        final TimerTask task = new KeyedObjectPoolMinIdleTimerTask<>(
-                keyedPool, key, minIdle);
-        getMinIdleTimer().schedule(task, 0L, period);
-        return task;
-    }
 
-    /**
-     * Periodically check the idle object count for each key in the
-     * {@code Collection keys} in the keyedPool. At most one idle object will be
-     * added per period.
-     *
-     * @param keyedPool
-     *            the keyedPool to check periodically.
-     * @param keys
-     *            a collection of keys to check the idle object count.
-     * @param minIdle
-     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less than
-     *            this then add an idle object.
-     * @param period
-     *            the frequency to check the number of idle objects in a
-     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a {@link Map} of key and {@link TimerTask} pairs that will
-     *         periodically check the pools idle object count.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code keys}, or any of the values in
-     *             the collection is {@code null} or when {@code minIdle} is
-     *             negative or when {@code period} isn't valid for
-     *             {@link Timer#schedule(TimerTask, long, long)}.
-     * @see #checkMinIdle(KeyedObjectPool, Object, int, long)
-     */
-    public static <K, V> Map<K, TimerTask> checkMinIdle(
-            final KeyedObjectPool<K, V> keyedPool, final Collection<K> keys,
-            final int minIdle, final long period)
-            throws IllegalArgumentException {
-        if (keys == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYS);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public V borrowObject(final K key) throws Exception,
+                NoSuchElementException, IllegalStateException {
+            return keyedPool.borrowObject(key);
         }
-        final Map<K, TimerTask> tasks = new HashMap<>(keys.size());
-        for (K key : keys) {
-            final TimerTask task = checkMinIdle(keyedPool, key, minIdle, period);
-            tasks.put(key, task);
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear() throws Exception, UnsupportedOperationException {
+            keyedPool.clear();
         }
-        return tasks;
-    }
 
-    /**
-     * Calls {@link ObjectPool#addObject()} on {@code pool} {@code count} number
-     * of times.
-     *
-     * @param pool
-     *            the pool to prefill.
-     * @param count
-     *            the number of idle objects to add.
-     * @param <T> the type of objects in the pool
-     * @throws Exception
-     *             when {@link ObjectPool#addObject()} fails.
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @deprecated Use {@link ObjectPool#addObjects(int)}.
-     */
-    @Deprecated
-    public static <T> void prefill(final ObjectPool<T> pool, final int count)
-            throws Exception, IllegalArgumentException {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear(final K key) throws Exception,
+                UnsupportedOperationException {
+            keyedPool.clear(key);
         }
-        pool.addObjects(count);
-    }
 
-    /**
-     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} with
-     * {@code key} {@code count} number of times.
-     *
-     * @param keyedPool
-     *            the keyedPool to prefill.
-     * @param key
-     *            the key to add objects for.
-     * @param count
-     *            the number of idle objects to add for {@code key}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws Exception
-     *             when {@link KeyedObjectPool#addObject(Object)} fails.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} or {@code key} is {@code null}.
-     * @deprecated Use {@link KeyedObjectPool#addObjects(Object, int)}.
-     */
-    @Deprecated
-    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
-            final K key, final int count) throws Exception,
-            IllegalArgumentException {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void close() {
+            try {
+                keyedPool.close();
+            } catch (final Exception e) {
+                // swallowed
+            }
         }
-        keyedPool.addObjects(key, count);
-    }
 
-    /**
-     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} with
-     * each key in {@code keys} for {@code count} number of times. This has
-     * the same effect as calling {@link #prefill(KeyedObjectPool, Object, int)}
-     * for each key in the {@code keys} collection.
-     *
-     * @param keyedPool
-     *            the keyedPool to prefill.
-     * @param keys
-     *            {@link Collection} of keys to add objects for.
-     * @param count
-     *            the number of idle objects to add for each {@code key}.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws Exception
-     *             when {@link KeyedObjectPool#addObject(Object)} fails.
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool}, {@code keys}, or any value in
-     *             {@code keys} is {@code null}.
-     * @see #prefill(KeyedObjectPool, Object, int)
-     * @deprecated Use {@link KeyedObjectPool#addObjects(Collection, int)}.
-     */
-    @Deprecated
-    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
-            final Collection<K> keys, final int count) throws Exception,
-            IllegalArgumentException {
-        if (keys == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYS);
+        /**
+         * Gets the eroding factor for the given key
+         *
+         * @param key
+         *            key
+         * @return eroding factor for the given keyed pool
+         */
+        protected ErodingFactor getErodingFactor(final K key) {
+            return erodingFactor;
         }
-        keyedPool.addObjects(keys, count);
-    }
 
-    /**
-     * Returns a synchronized (thread-safe) ObjectPool backed by the specified
-     * ObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
-     *
-     * @param pool
-     *            the ObjectPool to be "wrapped" in a synchronized ObjectPool.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @return a synchronized view of the specified ObjectPool.
-     */
-    public static <T> ObjectPool<T> synchronizedPool(final ObjectPool<T> pool) {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
-        }
-        /*
-         * assert !(pool instanceof GenericObjectPool) :
-         * "GenericObjectPool is already thread-safe"; assert !(pool instanceof
-         * SoftReferenceObjectPool) :
-         * "SoftReferenceObjectPool is already thread-safe"; assert !(pool
-         * instanceof StackObjectPool) :
-         * "StackObjectPool is already thread-safe"; assert
-         * !"org.apache.commons.pool.composite.CompositeObjectPool"
-         * .equals(pool.getClass().getName()) :
-         * "CompositeObjectPools are already thread-safe";
+        /**
+         * Gets the underlying pool
+         *
+         * @return the keyed pool that this ErodingKeyedObjectPool wraps
          */
-        return new SynchronizedObjectPool<>(pool);
-    }
+        protected KeyedObjectPool<K, V> getKeyedPool() {
+            return keyedPool;
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) KeyedObjectPool backed by the
-     * specified KeyedObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be "wrapped" in a synchronized
-     *            KeyedObjectPool.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a synchronized view of the specified KeyedObjectPool.
-     */
-    public static <K, V> KeyedObjectPool<K, V> synchronizedPool(
-            final KeyedObjectPool<K, V> keyedPool) {
-        /*
-         * assert !(keyedPool instanceof GenericKeyedObjectPool) :
-         * "GenericKeyedObjectPool is already thread-safe"; assert !(keyedPool
-         * instanceof StackKeyedObjectPool) :
-         * "StackKeyedObjectPool is already thread-safe"; assert
-         * !"org.apache.commons.pool.composite.CompositeKeyedObjectPool"
-         * .equals(keyedPool.getClass().getName()) :
-         * "CompositeKeyedObjectPools are already thread-safe";
+        /**
+         * {@inheritDoc}
          */
-        return new SynchronizedKeyedObjectPool<>(keyedPool);
-    }
+        @Override
+        public int getNumActive() {
+            return keyedPool.getNumActive();
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) PooledObjectFactory backed by the
-     * specified PooledObjectFactory.
-     *
-     * @param factory
-     *            the PooledObjectFactory to be "wrapped" in a synchronized
-     *            PooledObjectFactory.
-     * @param <T> the type of objects in the pool
-     * @return a synchronized view of the specified PooledObjectFactory.
-     */
-    public static <T> PooledObjectFactory<T> synchronizedPooledFactory(
-            final PooledObjectFactory<T> factory) {
-        return new SynchronizedPooledObjectFactory<>(factory);
-    }
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumActive(final K key) {
+            return keyedPool.getNumActive(key);
+        }
 
-    /**
-     * Returns a synchronized (thread-safe) KeyedPooledObjectFactory backed by
-     * the specified KeyedPoolableObjectFactory.
-     *
-     * @param keyedFactory
-     *            the KeyedPooledObjectFactory to be "wrapped" in a
-     *            synchronized KeyedPooledObjectFactory.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @return a synchronized view of the specified KeyedPooledObjectFactory.
-     */
-    public static <K, V> KeyedPooledObjectFactory<K, V> synchronizedKeyedPooledFactory(
-            final KeyedPooledObjectFactory<K, V> keyedFactory) {
-        return new SynchronizedKeyedPooledObjectFactory<>(keyedFactory);
-    }
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle() {
+            return keyedPool.getNumIdle();
+        }
 
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     *
-     * @param pool
-     *            the ObjectPool to be decorated so it shrinks its idle count
-     *            when possible.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null}.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(ObjectPool, float)
-     */
-    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool) {
-        return erodingPool(pool, 1f);
-    }
-
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     *
-     * @param pool
-     *            the ObjectPool to be decorated so it shrinks its idle count
-     *            when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param <T> the type of objects in the pool
-     * @throws IllegalArgumentException
-     *             when {@code pool} is {@code null} or when {@code factor} is
-     *             not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(ObjectPool)
-     */
-    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool,
-            final float factor) {
-        if (pool == null) {
-            throw new IllegalArgumentException(MSG_NULL_POOL);
-        }
-        if (factor <= 0f) {
-            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
-        }
-        return new ErodingObjectPool<>(pool, factor);
-    }
-
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null}.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool, float)
-     * @see #erodingPool(KeyedObjectPool, float, boolean)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool) {
-        return erodingPool(keyedPool, 1f);
-    }
-
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null} or when {@code factor}
-     *             is not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool, float, boolean)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool, final float factor) {
-        return erodingPool(keyedPool, factor, false);
-    }
-
-    /**
-     * Returns a pool that adaptively decreases its size when idle objects are
-     * no longer needed. This is intended as an always thread-safe alternative
-     * to using an idle object evictor provided by many pool implementations.
-     * This is also an effective way to shrink FIFO ordered pools that
-     * experience load spikes.
-     * <p>
-     * The factor parameter provides a mechanism to tweak the rate at which the
-     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
-     * try to shrink its size more often. Values greater than 1 cause the pool
-     * to less frequently try to shrink its size.
-     * </p>
-     * <p>
-     * The perKey parameter determines if the pool shrinks on a whole pool basis
-     * or a per key basis. When perKey is false, the keys do not have an effect
-     * on the rate at which the pool tries to shrink its size. When perKey is
-     * true, each key is shrunk independently.
-     * </p>
-     *
-     * @param keyedPool
-     *            the KeyedObjectPool to be decorated so it shrinks its idle
-     *            count when possible.
-     * @param factor
-     *            a positive value to scale the rate at which the pool tries to
-     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
-     *            shrinks more aggressively. If 1 &lt; factor then the pool
-     *            shrinks less aggressively.
-     * @param perKey
-     *            when true, each key is treated independently.
-     * @param <K> the type of the pool key
-     * @param <V> the type of pool entries
-     * @throws IllegalArgumentException
-     *             when {@code keyedPool} is {@code null} or when {@code factor}
-     *             is not positive.
-     * @return a pool that adaptively decreases its size when idle objects are
-     *         no longer needed.
-     * @see #erodingPool(KeyedObjectPool)
-     * @see #erodingPool(KeyedObjectPool, float)
-     */
-    public static <K, V> KeyedObjectPool<K, V> erodingPool(
-            final KeyedObjectPool<K, V> keyedPool, final float factor,
-            final boolean perKey) {
-        if (keyedPool == null) {
-            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
-        }
-        if (factor <= 0f) {
-            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
-        }
-        if (perKey) {
-            return new ErodingPerKeyKeyedObjectPool<>(keyedPool, factor);
-        }
-        return new ErodingKeyedObjectPool<>(keyedPool, factor);
-    }
-
-    /**
-     * Gets the {@code Timer} for checking keyedPool's idle count.
-     *
-     * @return the {@link Timer} for checking keyedPool's idle count.
-     */
-    private static Timer getMinIdleTimer() {
-        return TimerHolder.MIN_IDLE_TIMER;
-    }
-
-    /**
-     * Timer task that adds objects to the pool until the number of idle
-     * instances reaches the configured minIdle. Note that this is not the same
-     * as the pool's minIdle setting.
-     *
-     * @param <T> type of objects in the pool
-     */
-    private static final class ObjectPoolMinIdleTimerTask<T> extends TimerTask {
-
-        /** Minimum number of idle instances. Not the same as pool.getMinIdle(). */
-        private final int minIdle;
-
-        /** Object pool */
-        private final ObjectPool<T> pool;
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle(final K key) {
+            return keyedPool.getNumIdle(key);
+        }
 
         /**
-         * Create a new ObjectPoolMinIdleTimerTask for the given pool with the
-         * given minIdle setting.
-         *
-         * @param pool
-         *            object pool
-         * @param minIdle
-         *            number of idle instances to maintain
-         * @throws IllegalArgumentException
-         *             if the pool is null
+         * {@inheritDoc}
          */
-        ObjectPoolMinIdleTimerTask(final ObjectPool<T> pool, final int minIdle)
-                throws IllegalArgumentException {
-            if (pool == null) {
-                throw new IllegalArgumentException(MSG_NULL_POOL);
+        @Override
+        public void invalidateObject(final K key, final V obj) {
+            try {
+                keyedPool.invalidateObject(key, obj);
+            } catch (final Exception e) {
+                // swallowed
             }
-            this.pool = pool;
-            this.minIdle = minIdle;
         }
 
         /**
-         * {@inheritDoc}
+         * Returns obj to the pool, unless erosion is triggered, in which case
+         * obj is invalidated. Erosion is triggered when there are idle
+         * instances in the pool associated with the given key and more than the
+         * configured {@link #erodingFactor erosion factor} time has elapsed
+         * since the last returnObject activation.
+         *
+         * @param obj
+         *            object to return or invalidate
+         * @param key
+         *            key
+         * @see #erodingFactor
          */
         @Override
-        public void run() {
-            boolean success = false;
+        public void returnObject(final K key, final V obj) throws Exception {
+            boolean discard = false;
+            final long nowMillis = System.currentTimeMillis();
+            final ErodingFactor factor = getErodingFactor(key);
+            synchronized (keyedPool) {
+                if (factor.getNextShrink() < nowMillis) {
+                    final int numIdle = getNumIdle(key);
+                    if (numIdle > 0) {
+                        discard = true;
+                    }
+
+                    factor.update(nowMillis, numIdle);
+                }
+            }
             try {
-                if (pool.getNumIdle() < minIdle) {
-                    pool.addObject();
+                if (discard) {
+                    keyedPool.invalidateObject(key, obj);
+                } else {
+                    keyedPool.returnObject(key, obj);
                 }
-                success = true;
-
             } catch (final Exception e) {
-                cancel();
-            } finally {
-                // detect other types of Throwable and cancel this Timer
-                if (!success) {
-                    cancel();
-                }
+                // swallowed
             }
         }
 
@@ -632,28 +311,222 @@ public final class PoolUtils {
          */
         @Override
         public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("ObjectPoolMinIdleTimerTask");
-            sb.append("{minIdle=").append(minIdle);
-            sb.append(", pool=").append(pool);
-            sb.append('}');
-            return sb.toString();
+            return "ErodingKeyedObjectPool{" + "factor=" +
+                    erodingFactor + ", keyedPool=" + keyedPool + '}';
         }
     }
-
     /**
-     * Timer task that adds objects to the pool until the number of idle
-     * instances for the given key reaches the configured minIdle. Note that
-     * this is not the same as the pool's minIdle setting.
+     * Decorates an object pool, adding "eroding" behavior. Based on the
+     * configured {@link #factor erosion factor}, objects returning to the pool
+     * may be invalidated instead of being added to idle capacity.
      *
-     * @param <K> object pool key type
-     * @param <V> object pool value type
+     * @param <T> type of objects in the pool
      */
-    private static final class KeyedObjectPoolMinIdleTimerTask<K, V> extends
-            TimerTask {
-
-        /** Minimum number of idle instances. Not the same as pool.getMinIdle(). */
-        private final int minIdle;
+    private static class ErodingObjectPool<T> implements ObjectPool<T> {
+
+        /** Underlying object pool */
+        private final ObjectPool<T> pool;
+
+        /** Erosion factor */
+        private final ErodingFactor factor;
+
+        /**
+         * Creates an ErodingObjectPool wrapping the given pool using the
+         * specified erosion factor.
+         *
+         * @param pool
+         *            underlying pool
+         * @param factor
+         *            erosion factor - determines the frequency of erosion
+         *            events
+         * @see #factor
+         */
+        public ErodingObjectPool(final ObjectPool<T> pool, final float factor) {
+            this.pool = pool;
+            this.factor = new ErodingFactor(factor);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void addObject() throws Exception, IllegalStateException,
+                UnsupportedOperationException {
+            pool.addObject();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public T borrowObject() throws Exception, NoSuchElementException,
+                IllegalStateException {
+            return pool.borrowObject();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void clear() throws Exception, UnsupportedOperationException {
+            pool.clear();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void close() {
+            try {
+                pool.close();
+            } catch (final Exception e) {
+                // swallowed
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumActive() {
+            return pool.getNumActive();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getNumIdle() {
+            return pool.getNumIdle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invalidateObject(final T obj) {
+            try {
+                pool.invalidateObject(obj);
+            } catch (final Exception e) {
+                // swallowed
+            }
+        }
+
+        /**
+         * Returns * Gets obj to the pool, unless erosion is triggered, in which case
+         * obj is invalidated. Erosion is triggered when there are idle
+         * instances in the pool and more than the {@link #factor erosion
+         * factor}-determined time has elapsed since the last returnObject
+         * activation.
+         *
+         * @param obj
+         *            object to return or invalidate
+         * @see #factor
+         */
+        @Override
+        public void returnObject(final T obj) {
+            boolean discard = false;
+            final long nowMillis = System.currentTimeMillis();
+            synchronized (pool) {
+                if (factor.getNextShrink() < nowMillis) { // XXX: Pool 3: move test
+                                                    // out of sync block
+                    final int numIdle = pool.getNumIdle();
+                    if (numIdle > 0) {
+                        discard = true;
+                    }
+
+                    factor.update(nowMillis, numIdle);
+                }
+            }
+            try {
+                if (discard) {
+                    pool.invalidateObject(obj);
+                } else {
+                    pool.returnObject(obj);
+                }
+            } catch (final Exception e) {
+                // swallowed
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return "ErodingObjectPool{" + "factor=" + factor + ", pool=" +
+                    pool + '}';
+        }
+    }
+    /**
+     * Extends ErodingKeyedObjectPool to allow erosion to take place on a
+     * per-key basis. Timing of erosion events is tracked separately for
+     * separate keyed pools.
+     *
+     * @param <K> object pool key type
+     * @param <V> object pool value type
+     */
+    private static final class ErodingPerKeyKeyedObjectPool<K, V> extends
+            ErodingKeyedObjectPool<K, V> {
+
+        /** Erosion factor - same for all pools */
+        private final float factor;
+
+        /** Map of ErodingFactor instances keyed on pool keys */
+        private final Map<K, ErodingFactor> factors = Collections.synchronizedMap(new HashMap<>());
+
+        /**
+         * Creates a new ErordingPerKeyKeyedObjectPool decorating the given keyed
+         * pool with the specified erosion factor.
+         *
+         * @param keyedPool
+         *            underlying keyed pool
+         * @param factor
+         *            erosion factor
+         */
+        public ErodingPerKeyKeyedObjectPool(
+                final KeyedObjectPool<K, V> keyedPool, final float factor) {
+            super(keyedPool, null);
+            this.factor = factor;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected ErodingFactor getErodingFactor(final K key) {
+            ErodingFactor eFactor = factors.get(key);
+            // this may result in two ErodingFactors being created for a key
+            // since they are small and cheap this is okay.
+            if (eFactor == null) {
+                eFactor = new ErodingFactor(this.factor);
+                factors.put(key, eFactor);
+            }
+            return eFactor;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public String toString() {
+            return "ErodingPerKeyKeyedObjectPool{" + "factor=" + factor +
+                    ", keyedPool=" + getKeyedPool() + '}';
+        }
+    }
+    /**
+     * Timer task that adds objects to the pool until the number of idle
+     * instances for the given key reaches the configured minIdle. Note that
+     * this is not the same as the pool's minIdle setting.
+     *
+     * @param <K> object pool key type
+     * @param <V> object pool value type
+     */
+    private static final class KeyedObjectPoolMinIdleTimerTask<K, V> extends
+            TimerTask {
+
+        /** Minimum number of idle instances. Not the same as pool.getMinIdle(). */
+        private final int minIdle;
 
         /** Key to ensure minIdle for */
         private final K key;
@@ -721,166 +594,60 @@ public final class PoolUtils {
             return sb.toString();
         }
     }
-
     /**
-     * A synchronized (thread-safe) ObjectPool backed by the specified
-     * ObjectPool.
-     * <p>
-     * <b>Note:</b> This should not be used on pool implementations that already
-     * provide proper synchronization such as the pools provided in the Commons
-     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
-     * objects to be returned before allowing another one to be borrowed with
-     * another layer of synchronization will cause liveliness issues or a
-     * deadlock.
-     * </p>
+     * Timer task that adds objects to the pool until the number of idle
+     * instances reaches the configured minIdle. Note that this is not the same
+     * as the pool's minIdle setting.
      *
      * @param <T> type of objects in the pool
      */
-    private static final class SynchronizedObjectPool<T> implements ObjectPool<T> {
+    private static final class ObjectPoolMinIdleTimerTask<T> extends TimerTask {
 
-        /**
-         * Object whose monitor is used to synchronize methods on the wrapped
-         * pool.
-         */
-        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+        /** Minimum number of idle instances. Not the same as pool.getMinIdle(). */
+        private final int minIdle;
 
-        /** the underlying object pool */
+        /** Object pool */
         private final ObjectPool<T> pool;
 
         /**
-         * Creates a new SynchronizedObjectPool wrapping the given pool.
+         * Constructs a new ObjectPoolMinIdleTimerTask for the given pool with the
+         * given minIdle setting.
          *
          * @param pool
-         *            the ObjectPool to be "wrapped" in a synchronized
-         *            ObjectPool.
+         *            object pool
+         * @param minIdle
+         *            number of idle instances to maintain
          * @throws IllegalArgumentException
          *             if the pool is null
          */
-        SynchronizedObjectPool(final ObjectPool<T> pool)
+        ObjectPoolMinIdleTimerTask(final ObjectPool<T> pool, final int minIdle)
                 throws IllegalArgumentException {
             if (pool == null) {
                 throw new IllegalArgumentException(MSG_NULL_POOL);
             }
             this.pool = pool;
+            this.minIdle = minIdle;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public T borrowObject() throws Exception, NoSuchElementException,
-                IllegalStateException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
+        public void run() {
+            boolean success = false;
             try {
-                return pool.borrowObject();
+                if (pool.getNumIdle() < minIdle) {
+                    pool.addObject();
+                }
+                success = true;
+
+            } catch (final Exception e) {
+                cancel();
             } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void returnObject(final T obj) {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.returnObject(obj);
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void invalidateObject(final T obj) {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.invalidateObject(obj);
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void addObject() throws Exception, IllegalStateException,
-                UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.addObject();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            final ReadLock readLock = readWriteLock.readLock();
-            readLock.lock();
-            try {
-                return pool.getNumIdle();
-            } finally {
-                readLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive() {
-            final ReadLock readLock = readWriteLock.readLock();
-            readLock.lock();
-            try {
-                return pool.getNumActive();
-            } finally {
-                readLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.clear();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void close() {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                pool.close();
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
-            } finally {
-                writeLock.unlock();
+                // detect other types of Throwable and cancel this Timer
+                if (!success) {
+                    cancel();
+                }
             }
         }
 
@@ -890,8 +657,9 @@ public final class PoolUtils {
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedObjectPool");
-            sb.append("{pool=").append(pool);
+            sb.append("ObjectPoolMinIdleTimerTask");
+            sb.append("{minIdle=").append(minIdle);
+            sb.append(", pool=").append(pool);
             sb.append('}');
             return sb.toString();
         }
@@ -945,6 +713,21 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
+        public void addObject(final K key) throws Exception,
+                IllegalStateException, UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                keyedPool.addObject(key);
+            } finally {
+                writeLock.unlock();
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
         public V borrowObject(final K key) throws Exception,
                 NoSuchElementException, IllegalStateException {
             final WriteLock writeLock = readWriteLock.writeLock();
@@ -960,13 +743,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void returnObject(final K key, final V obj) {
+        public void clear() throws Exception, UnsupportedOperationException {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.returnObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed
+                keyedPool.clear();
             } finally {
                 writeLock.unlock();
             }
@@ -976,13 +757,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void invalidateObject(final K key, final V obj) {
+        public void clear(final K key) throws Exception,
+                UnsupportedOperationException {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.invalidateObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed as of Pool 2
+                keyedPool.clear(key);
             } finally {
                 writeLock.unlock();
             }
@@ -992,12 +772,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void addObject(final K key) throws Exception,
-                IllegalStateException, UnsupportedOperationException {
+        public void close() {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.addObject(key);
+                keyedPool.close();
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1007,11 +788,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public int getNumIdle(final K key) {
+        public int getNumActive() {
             final ReadLock readLock = readWriteLock.readLock();
             readLock.lock();
             try {
-                return keyedPool.getNumIdle(key);
+                return keyedPool.getNumActive();
             } finally {
                 readLock.unlock();
             }
@@ -1049,11 +830,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public int getNumActive() {
+        public int getNumIdle(final K key) {
             final ReadLock readLock = readWriteLock.readLock();
             readLock.lock();
             try {
-                return keyedPool.getNumActive();
+                return keyedPool.getNumIdle(key);
             } finally {
                 readLock.unlock();
             }
@@ -1063,26 +844,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            final WriteLock writeLock = readWriteLock.writeLock();
-            writeLock.lock();
-            try {
-                keyedPool.clear();
-            } finally {
-                writeLock.unlock();
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear(final K key) throws Exception,
-                UnsupportedOperationException {
+        public void invalidateObject(final K key, final V obj) {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.clear(key);
+                keyedPool.invalidateObject(key, obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1092,13 +860,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void close() {
+        public void returnObject(final K key, final V obj) {
             final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedPool.close();
+                keyedPool.returnObject(key, obj);
             } catch (final Exception e) {
-                // swallowed as of Pool 2
+                // swallowed
             } finally {
                 writeLock.unlock();
             }
@@ -1118,8 +886,8 @@ public final class PoolUtils {
     }
 
     /**
-     * A fully synchronized PooledObjectFactory that wraps a
-     * PooledObjectFactory and synchronizes access to the wrapped factory
+     * A fully synchronized KeyedPooledObjectFactory that wraps a
+     * KeyedPooledObjectFactory and synchronizes access to the wrapped factory
      * methods.
      * <p>
      * <b>Note:</b> This should not be used on pool implementations that already
@@ -1127,42 +895,45 @@ public final class PoolUtils {
      * Pool library.
      * </p>
      *
-     * @param <T> pooled object factory type
+     * @param <K> pooled object factory key type
+     * @param <V> pooled object factory key value
      */
-    private static final class SynchronizedPooledObjectFactory<T> implements
-            PooledObjectFactory<T> {
+    private static final class SynchronizedKeyedPooledObjectFactory<K, V>
+            implements KeyedPooledObjectFactory<K, V> {
 
         /** Synchronization lock */
         private final WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
 
         /** Wrapped factory */
-        private final PooledObjectFactory<T> factory;
+        private final KeyedPooledObjectFactory<K, V> keyedFactory;
 
         /**
-         * Creates a SynchronizedPoolableObjectFactory wrapping the given
+         * Creates a SynchronizedKeyedPoolableObjectFactory wrapping the given
          * factory.
          *
-         * @param factory
+         * @param keyedFactory
          *            underlying factory to wrap
          * @throws IllegalArgumentException
          *             if the factory is null
          */
-        SynchronizedPooledObjectFactory(final PooledObjectFactory<T> factory)
+        SynchronizedKeyedPooledObjectFactory(
+                final KeyedPooledObjectFactory<K, V> keyedFactory)
                 throws IllegalArgumentException {
-            if (factory == null) {
-                throw new IllegalArgumentException("factory must not be null.");
+            if (keyedFactory == null) {
+                throw new IllegalArgumentException(
+                        "keyedFactory must not be null.");
             }
-            this.factory = factory;
+            this.keyedFactory = keyedFactory;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public PooledObject<T> makeObject() throws Exception {
+        public void activateObject(final K key, final PooledObject<V> p) throws Exception {
             writeLock.lock();
             try {
-                return factory.makeObject();
+                keyedFactory.activateObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1172,10 +943,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void destroyObject(final PooledObject<T> p) throws Exception {
+        public void destroyObject(final K key, final PooledObject<V> p) throws Exception {
             writeLock.lock();
             try {
-                factory.destroyObject(p);
+                keyedFactory.destroyObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1185,10 +956,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public boolean validateObject(final PooledObject<T> p) {
+        public PooledObject<V> makeObject(final K key) throws Exception {
             writeLock.lock();
             try {
-                return factory.validateObject(p);
+                return keyedFactory.makeObject(key);
             } finally {
                 writeLock.unlock();
             }
@@ -1198,10 +969,10 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void activateObject(final PooledObject<T> p) throws Exception {
+        public void passivateObject(final K key, final PooledObject<V> p) throws Exception {
             writeLock.lock();
             try {
-                factory.activateObject(p);
+                keyedFactory.passivateObject(key, p);
             } finally {
                 writeLock.unlock();
             }
@@ -1211,77 +982,80 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void passivateObject(final PooledObject<T> p) throws Exception {
-            writeLock.lock();
-            try {
-                factory.passivateObject(p);
-            } finally {
-                writeLock.unlock();
-            }
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedKeyedPoolableObjectFactory");
+            sb.append("{keyedFactory=").append(keyedFactory);
+            sb.append('}');
+            return sb.toString();
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedPoolableObjectFactory");
-            sb.append("{factory=").append(factory);
-            sb.append('}');
-            return sb.toString();
+        public boolean validateObject(final K key, final PooledObject<V> p) {
+            writeLock.lock();
+            try {
+                return keyedFactory.validateObject(key, p);
+            } finally {
+                writeLock.unlock();
+            }
         }
     }
 
     /**
-     * A fully synchronized KeyedPooledObjectFactory that wraps a
-     * KeyedPooledObjectFactory and synchronizes access to the wrapped factory
-     * methods.
+     * A synchronized (thread-safe) ObjectPool backed by the specified
+     * ObjectPool.
      * <p>
      * <b>Note:</b> This should not be used on pool implementations that already
      * provide proper synchronization such as the pools provided in the Commons
-     * Pool library.
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
      * </p>
      *
-     * @param <K> pooled object factory key type
-     * @param <V> pooled object factory key value
+     * @param <T> type of objects in the pool
      */
-    private static final class SynchronizedKeyedPooledObjectFactory<K, V>
-            implements KeyedPooledObjectFactory<K, V> {
+    private static final class SynchronizedObjectPool<T> implements ObjectPool<T> {
 
-        /** Synchronization lock */
-        private final WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
+        /**
+         * Object whose monitor is used to synchronize methods on the wrapped
+         * pool.
+         */
+        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 
-        /** Wrapped factory */
-        private final KeyedPooledObjectFactory<K, V> keyedFactory;
+        /** the underlying object pool */
+        private final ObjectPool<T> pool;
 
         /**
-         * Creates a SynchronizedKeyedPoolableObjectFactory wrapping the given
-         * factory.
+         * Creates a new SynchronizedObjectPool wrapping the given pool.
          *
-         * @param keyedFactory
-         *            underlying factory to wrap
+         * @param pool
+         *            the ObjectPool to be "wrapped" in a synchronized
+         *            ObjectPool.
          * @throws IllegalArgumentException
-         *             if the factory is null
+         *             if the pool is null
          */
-        SynchronizedKeyedPooledObjectFactory(
-                final KeyedPooledObjectFactory<K, V> keyedFactory)
+        SynchronizedObjectPool(final ObjectPool<T> pool)
                 throws IllegalArgumentException {
-            if (keyedFactory == null) {
-                throw new IllegalArgumentException(
-                        "keyedFactory must not be null.");
+            if (pool == null) {
+                throw new IllegalArgumentException(MSG_NULL_POOL);
             }
-            this.keyedFactory = keyedFactory;
+            this.pool = pool;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public PooledObject<V> makeObject(final K key) throws Exception {
+        public void addObject() throws Exception, IllegalStateException,
+                UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                return keyedFactory.makeObject(key);
+                pool.addObject();
             } finally {
                 writeLock.unlock();
             }
@@ -1291,10 +1065,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void destroyObject(final K key, final PooledObject<V> p) throws Exception {
+        public T borrowObject() throws Exception, NoSuchElementException,
+                IllegalStateException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedFactory.destroyObject(key, p);
+                return pool.borrowObject();
             } finally {
                 writeLock.unlock();
             }
@@ -1304,10 +1080,11 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public boolean validateObject(final K key, final PooledObject<V> p) {
+        public void clear() throws Exception, UnsupportedOperationException {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                return keyedFactory.validateObject(key, p);
+                pool.clear();
             } finally {
                 writeLock.unlock();
             }
@@ -1317,10 +1094,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void activateObject(final K key, final PooledObject<V> p) throws Exception {
+        public void close() {
+            final WriteLock writeLock = readWriteLock.writeLock();
             writeLock.lock();
             try {
-                keyedFactory.activateObject(key, p);
+                pool.close();
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
             } finally {
                 writeLock.unlock();
             }
@@ -1330,12 +1110,13 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void passivateObject(final K key, final PooledObject<V> p) throws Exception {
-            writeLock.lock();
+        public int getNumActive() {
+            final ReadLock readLock = readWriteLock.readLock();
+            readLock.lock();
             try {
-                keyedFactory.passivateObject(key, p);
+                return pool.getNumActive();
             } finally {
-                writeLock.unlock();
+                readLock.unlock();
             }
         }
 
@@ -1343,76 +1124,46 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            sb.append("SynchronizedKeyedPoolableObjectFactory");
-            sb.append("{keyedFactory=").append(keyedFactory);
-            sb.append('}');
-            return sb.toString();
-        }
-    }
-
-    /**
-     * Encapsulate the logic for when the next poolable object should be
-     * discarded. Each time update is called, the next time to shrink is
-     * recomputed, based on the float factor, number of idle instances in the
-     * pool and high water mark. Float factor is assumed to be between 0 and 1.
-     * Values closer to 1 cause less frequent erosion events. Erosion event
-     * timing also depends on numIdle. When this value is relatively high (close
-     * to previously established high water mark), erosion occurs more
-     * frequently.
-     */
-    private static final class ErodingFactor {
-        /** Determines frequency of "erosion" events */
-        private final float factor;
-
-        /** Time of next shrink event */
-        private transient volatile long nextShrink;
-
-        /** High water mark - largest numIdle encountered */
-        private transient volatile int idleHighWaterMark;
-
-        /**
-         * Creates a new ErodingFactor with the given erosion factor.
-         *
-         * @param factor
-         *            erosion factor
-         */
-        public ErodingFactor(final float factor) {
-            this.factor = factor;
-            nextShrink = System.currentTimeMillis() + (long) (900000 * factor); // now
-                                                                                // +
-                                                                                // 15
-                                                                                // min
-                                                                                // *
-                                                                                // factor
-            idleHighWaterMark = 1;
+        public int getNumIdle() {
+            final ReadLock readLock = readWriteLock.readLock();
+            readLock.lock();
+            try {
+                return pool.getNumIdle();
+            } finally {
+                readLock.unlock();
+            }
         }
 
         /**
-         * Updates internal state using the supplied time and numIdle.
-         *
-         * @param now
-         *            current time
-         * @param numIdle
-         *            number of idle elements in the pool
+         * {@inheritDoc}
          */
-        public void update(final long now, final int numIdle) {
-            final int idle = Math.max(0, numIdle);
-            idleHighWaterMark = Math.max(idle, idleHighWaterMark);
-            final float maxInterval = 15f;
-            final float minutes = maxInterval +
-                    ((1f - maxInterval) / idleHighWaterMark) * idle;
-            nextShrink = now + (long) (minutes * 60000f * factor);
+        @Override
+        public void invalidateObject(final T obj) {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                pool.invalidateObject(obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
-         * Returns the time of the next erosion event.
-         *
-         * @return next shrink time
+         * {@inheritDoc}
          */
-        public long getNextShrink() {
-            return nextShrink;
+        @Override
+        public void returnObject(final T obj) {
+            final WriteLock writeLock = readWriteLock.writeLock();
+            writeLock.lock();
+            try {
+                pool.returnObject(obj);
+            } catch (final Exception e) {
+                // swallowed as of Pool 2
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
@@ -1420,85 +1171,62 @@ public final class PoolUtils {
          */
         @Override
         public String toString() {
-            return "ErodingFactor{" + "factor=" + factor +
-                    ", idleHighWaterMark=" + idleHighWaterMark + '}';
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedObjectPool");
+            sb.append("{pool=").append(pool);
+            sb.append('}');
+            return sb.toString();
         }
     }
 
     /**
-     * Decorates an object pool, adding "eroding" behavior. Based on the
-     * configured {@link #factor erosion factor}, objects returning to the pool
-     * may be invalidated instead of being added to idle capacity.
+     * A fully synchronized PooledObjectFactory that wraps a
+     * PooledObjectFactory and synchronizes access to the wrapped factory
+     * methods.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library.
+     * </p>
      *
-     * @param <T> type of objects in the pool
+     * @param <T> pooled object factory type
      */
-    private static class ErodingObjectPool<T> implements ObjectPool<T> {
+    private static final class SynchronizedPooledObjectFactory<T> implements
+            PooledObjectFactory<T> {
 
-        /** Underlying object pool */
-        private final ObjectPool<T> pool;
+        /** Synchronization lock */
+        private final WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
 
-        /** Erosion factor */
-        private final ErodingFactor factor;
+        /** Wrapped factory */
+        private final PooledObjectFactory<T> factory;
 
         /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
+         * Creates a SynchronizedPoolableObjectFactory wrapping the given
+         * factory.
          *
-         * @param pool
-         *            underlying pool
-         * @param factor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #factor
+         * @param factory
+         *            underlying factory to wrap
+         * @throws IllegalArgumentException
+         *             if the factory is null
          */
-        public ErodingObjectPool(final ObjectPool<T> pool, final float factor) {
-            this.pool = pool;
-            this.factor = new ErodingFactor(factor);
+        SynchronizedPooledObjectFactory(final PooledObjectFactory<T> factory)
+                throws IllegalArgumentException {
+            if (factory == null) {
+                throw new IllegalArgumentException("factory must not be null.");
+            }
+            this.factory = factory;
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public T borrowObject() throws Exception, NoSuchElementException,
-                IllegalStateException {
-            return pool.borrowObject();
-        }
-
-        /**
-         * Returns obj to the pool, unless erosion is triggered, in which case
-         * obj is invalidated. Erosion is triggered when there are idle
-         * instances in the pool and more than the {@link #factor erosion
-         * factor}-determined time has elapsed since the last returnObject
-         * activation.
-         *
-         * @param obj
-         *            object to return or invalidate
-         * @see #factor
-         */
-        @Override
-        public void returnObject(final T obj) {
-            boolean discard = false;
-            final long now = System.currentTimeMillis();
-            synchronized (pool) {
-                if (factor.getNextShrink() < now) { // XXX: Pool 3: move test
-                                                    // out of sync block
-                    final int numIdle = pool.getNumIdle();
-                    if (numIdle > 0) {
-                        discard = true;
-                    }
-
-                    factor.update(now, numIdle);
-                }
-            }
+        public void activateObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
             try {
-                if (discard) {
-                    pool.invalidateObject(obj);
-                } else {
-                    pool.returnObject(obj);
-                }
-            } catch (final Exception e) {
-                // swallowed
+                factory.activateObject(p);
+            } finally {
+                writeLock.unlock();
             }
         }
 
@@ -1506,11 +1234,12 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void invalidateObject(final T obj) {
+        public void destroyObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
             try {
-                pool.invalidateObject(obj);
-            } catch (final Exception e) {
-                // swallowed
+                factory.destroyObject(p);
+            } finally {
+                writeLock.unlock();
             }
         }
 
@@ -1518,325 +1247,592 @@ public final class PoolUtils {
          * {@inheritDoc}
          */
         @Override
-        public void addObject() throws Exception, IllegalStateException,
-                UnsupportedOperationException {
-            pool.addObject();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            return pool.getNumIdle();
+        public PooledObject<T> makeObject() throws Exception {
+            writeLock.lock();
+            try {
+                return factory.makeObject();
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public int getNumActive() {
-            return pool.getNumActive();
+        public void passivateObject(final PooledObject<T> p) throws Exception {
+            writeLock.lock();
+            try {
+                factory.passivateObject(p);
+            } finally {
+                writeLock.unlock();
+            }
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            pool.clear();
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("SynchronizedPoolableObjectFactory");
+            sb.append("{factory=").append(factory);
+            sb.append('}');
+            return sb.toString();
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        public void close() {
+        public boolean validateObject(final PooledObject<T> p) {
+            writeLock.lock();
             try {
-                pool.close();
-            } catch (final Exception e) {
-                // swallowed
+                return factory.validateObject(p);
+            } finally {
+                writeLock.unlock();
             }
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public String toString() {
-            return "ErodingObjectPool{" + "factor=" + factor + ", pool=" +
-                    pool + '}';
-        }
     }
 
     /**
-     * Decorates a keyed object pool, adding "eroding" behavior. Based on the
-     * configured erosion factor, objects returning to the pool
-     * may be invalidated instead of being added to idle capacity.
-     *
-     * @param <K> object pool key type
-     * @param <V> object pool value type
+     * Timer used to periodically check pools idle object count. Because a
+     * {@link Timer} creates a {@link Thread}, an IODH is used.
      */
-    private static class ErodingKeyedObjectPool<K, V> implements
-            KeyedObjectPool<K, V> {
+    static class TimerHolder {
+        static final Timer MIN_IDLE_TIMER = new Timer(true);
+    }
 
-        /** Underlying pool */
-        private final KeyedObjectPool<K, V> keyedPool;
+    private static final String MSG_FACTOR_NEGATIVE = "factor must be positive.";
 
-        /** Erosion factor */
-        private final ErodingFactor erodingFactor;
+    private static final String MSG_MIN_IDLE = "minIdle must be non-negative.";
 
-        /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying pool
-         * @param factor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #erodingFactor
-         */
-        public ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
-                final float factor) {
-            this(keyedPool, new ErodingFactor(factor));
-        }
+    static final String MSG_NULL_KEY = "key must not be null.";
 
-        /**
-         * Creates an ErodingObjectPool wrapping the given pool using the
-         * specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying pool - must not be null
-         * @param erodingFactor
-         *            erosion factor - determines the frequency of erosion
-         *            events
-         * @see #erodingFactor
-         */
-        protected ErodingKeyedObjectPool(final KeyedObjectPool<K, V> keyedPool,
-                final ErodingFactor erodingFactor) {
-            if (keyedPool == null) {
-                throw new IllegalArgumentException(
-                        MSG_NULL_KEYED_POOL);
-            }
-            this.keyedPool = keyedPool;
-            this.erodingFactor = erodingFactor;
-        }
+    private static final String MSG_NULL_KEYED_POOL = "keyedPool must not be null.";
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public V borrowObject(final K key) throws Exception,
-                NoSuchElementException, IllegalStateException {
-            return keyedPool.borrowObject(key);
-        }
+    static final String MSG_NULL_KEYS = "keys must not be null.";
 
-        /**
-         * Returns obj to the pool, unless erosion is triggered, in which case
-         * obj is invalidated. Erosion is triggered when there are idle
-         * instances in the pool associated with the given key and more than the
-         * configured {@link #erodingFactor erosion factor} time has elapsed
-         * since the last returnObject activation.
-         *
-         * @param obj
-         *            object to return or invalidate
-         * @param key
-         *            key
-         * @see #erodingFactor
-         */
-        @Override
-        public void returnObject(final K key, final V obj) throws Exception {
-            boolean discard = false;
-            final long now = System.currentTimeMillis();
-            final ErodingFactor factor = getErodingFactor(key);
-            synchronized (keyedPool) {
-                if (factor.getNextShrink() < now) {
-                    final int numIdle = getNumIdle(key);
-                    if (numIdle > 0) {
-                        discard = true;
-                    }
+    private static final String MSG_NULL_POOL = "pool must not be null.";
 
-                    factor.update(now, numIdle);
-                }
-            }
-            try {
-                if (discard) {
-                    keyedPool.invalidateObject(key, obj);
-                } else {
-                    keyedPool.returnObject(key, obj);
-                }
-            } catch (final Exception e) {
-                // swallowed
-            }
+    /**
+     * Periodically check the idle object count for each key in the
+     * {@code Collection keys} in the keyedPool. At most one idle object will be
+     * added per period.
+     *
+     * @param keyedPool
+     *            the keyedPool to check periodically.
+     * @param keys
+     *            a collection of keys to check the idle object count.
+     * @param minIdle
+     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less than
+     *            this then add an idle object.
+     * @param periodMillis
+     *            the frequency in milliseconds to check the number of idle objects in a
+     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a {@link Map} of key and {@link TimerTask} pairs that will
+     *         periodically check the pools idle object count.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code keys}, or any of the values in
+     *             the collection is {@code null} or when {@code minIdle} is
+     *             negative or when {@code period} isn't valid for
+     *             {@link Timer#schedule(TimerTask, long, long)}.
+     * @see #checkMinIdle(KeyedObjectPool, Object, int, long)
+     */
+    public static <K, V> Map<K, TimerTask> checkMinIdle(
+            final KeyedObjectPool<K, V> keyedPool, final Collection<K> keys,
+            final int minIdle, final long periodMillis)
+            throws IllegalArgumentException {
+        if (keys == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYS);
         }
-
-        /**
-         * Returns the eroding factor for the given key
-         *
-         * @param key
-         *            key
-         * @return eroding factor for the given keyed pool
-         */
-        protected ErodingFactor getErodingFactor(final K key) {
-            return erodingFactor;
+        final Map<K, TimerTask> tasks = new HashMap<>(keys.size());
+        for (K key : keys) {
+            final TimerTask task = checkMinIdle(keyedPool, key, minIdle, periodMillis);
+            tasks.put(key, task);
         }
+        return tasks;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void invalidateObject(final K key, final V obj) {
-            try {
-                keyedPool.invalidateObject(key, obj);
-            } catch (final Exception e) {
-                // swallowed
-            }
+    /**
+     * Periodically check the idle object count for the key in the keyedPool. At
+     * most one idle object will be added per period. If there is an exception
+     * when calling {@link KeyedObjectPool#addObject(Object)} then no more
+     * checks for that key will be performed.
+     *
+     * @param keyedPool
+     *            the keyedPool to check periodically.
+     * @param key
+     *            the key to check the idle count of.
+     * @param minIdle
+     *            if the {@link KeyedObjectPool#getNumIdle(Object)} is less than
+     *            this then add an idle object.
+     * @param periodMillis
+     *            the frequency in milliseconds to check the number of idle objects in a
+     *            keyedPool, see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return the {@link TimerTask} that will periodically check the pools idle
+     *         object count.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code key} is {@code null} or
+     *             when {@code minIdle} is negative or when {@code period} isn't
+     *             valid for {@link Timer#schedule(TimerTask, long, long)}.
+     */
+    public static <K, V> TimerTask checkMinIdle(
+            final KeyedObjectPool<K, V> keyedPool, final K key,
+            final int minIdle, final long periodMillis)
+            throws IllegalArgumentException {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void addObject(final K key) throws Exception,
-                IllegalStateException, UnsupportedOperationException {
-            keyedPool.addObject(key);
+        if (key == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEY);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle() {
-            return keyedPool.getNumIdle();
+        if (minIdle < 0) {
+            throw new IllegalArgumentException(MSG_MIN_IDLE);
         }
+        final TimerTask task = new KeyedObjectPoolMinIdleTimerTask<>(
+                keyedPool, key, minIdle);
+        getMinIdleTimer().schedule(task, 0L, periodMillis);
+        return task;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumIdle(final K key) {
-            return keyedPool.getNumIdle(key);
+    /**
+     * Periodically check the idle object count for the pool. At most one idle
+     * object will be added per period. If there is an exception when calling
+     * {@link ObjectPool#addObject()} then no more checks will be performed.
+     *
+     * @param pool
+     *            the pool to check periodically.
+     * @param minIdle
+     *            if the {@link ObjectPool#getNumIdle()} is less than this then
+     *            add an idle object.
+     * @param periodMillis
+     *            the frequency in milliseconds to check the number of idle objects in a pool,
+     *            see {@link Timer#schedule(TimerTask, long, long)}.
+     * @param <T> the type of objects in the pool
+     * @return the {@link TimerTask} that will periodically check the pools idle
+     *         object count.
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null} or when {@code minIdle} is
+     *             negative or when {@code period} isn't valid for
+     *             {@link Timer#schedule(TimerTask, long, long)}
+     */
+    public static <T> TimerTask checkMinIdle(final ObjectPool<T> pool,
+            final int minIdle, final long periodMillis)
+            throws IllegalArgumentException {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive() {
-            return keyedPool.getNumActive();
+        if (minIdle < 0) {
+            throw new IllegalArgumentException(MSG_MIN_IDLE);
         }
+        final TimerTask task = new ObjectPoolMinIdleTimerTask<>(pool, minIdle);
+        getMinIdleTimer().schedule(task, 0L, periodMillis);
+        return task;
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getNumActive(final K key) {
-            return keyedPool.getNumActive(key);
+    /**
+     * Should the supplied Throwable be re-thrown (eg if it is an instance of
+     * one of the Throwables that should never be swallowed). Used by the pool
+     * error handling for operations that throw exceptions that normally need to
+     * be ignored.
+     *
+     * @param t
+     *            The Throwable to check
+     * @throws ThreadDeath
+     *             if that is passed in
+     * @throws VirtualMachineError
+     *             if that is passed in
+     */
+    public static void checkRethrow(final Throwable t) {
+        if (t instanceof ThreadDeath) {
+            throw (ThreadDeath) t;
+        }
+        if (t instanceof VirtualMachineError) {
+            throw (VirtualMachineError) t;
         }
+        // All other instances of Throwable will be silently swallowed
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear() throws Exception, UnsupportedOperationException {
-            keyedPool.clear();
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null}.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool, float)
+     * @see #erodingPool(KeyedObjectPool, float, boolean)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool) {
+        return erodingPool(keyedPool, 1f);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null} or when {@code factor}
+     *             is not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool, float, boolean)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool, final float factor) {
+        return erodingPool(keyedPool, factor, false);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     * <p>
+     * The perKey parameter determines if the pool shrinks on a whole pool basis
+     * or a per key basis. When perKey is false, the keys do not have an effect
+     * on the rate at which the pool tries to shrink its size. When perKey is
+     * true, each key is shrunk independently.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be decorated so it shrinks its idle
+     *            count when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param perKey
+     *            when true, each key is treated independently.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} is {@code null} or when {@code factor}
+     *             is not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(KeyedObjectPool)
+     * @see #erodingPool(KeyedObjectPool, float)
+     */
+    public static <K, V> KeyedObjectPool<K, V> erodingPool(
+            final KeyedObjectPool<K, V> keyedPool, final float factor,
+            final boolean perKey) {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
+        if (factor <= 0f) {
+            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
+        }
+        if (perKey) {
+            return new ErodingPerKeyKeyedObjectPool<>(keyedPool, factor);
+        }
+        return new ErodingKeyedObjectPool<>(keyedPool, factor);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void clear(final K key) throws Exception,
-                UnsupportedOperationException {
-            keyedPool.clear(key);
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     *
+     * @param pool
+     *            the ObjectPool to be decorated so it shrinks its idle count
+     *            when possible.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(ObjectPool, float)
+     */
+    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool) {
+        return erodingPool(pool, 1f);
+    }
+
+    /**
+     * Returns a pool that adaptively decreases its size when idle objects are
+     * no longer needed. This is intended as an always thread-safe alternative
+     * to using an idle object evictor provided by many pool implementations.
+     * This is also an effective way to shrink FIFO ordered pools that
+     * experience load spikes.
+     * <p>
+     * The factor parameter provides a mechanism to tweak the rate at which the
+     * pool tries to shrink its size. Values between 0 and 1 cause the pool to
+     * try to shrink its size more often. Values greater than 1 cause the pool
+     * to less frequently try to shrink its size.
+     * </p>
+     *
+     * @param pool
+     *            the ObjectPool to be decorated so it shrinks its idle count
+     *            when possible.
+     * @param factor
+     *            a positive value to scale the rate at which the pool tries to
+     *            reduce its size. If 0 &lt; factor &lt; 1 then the pool
+     *            shrinks more aggressively. If 1 &lt; factor then the pool
+     *            shrinks less aggressively.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null} or when {@code factor} is
+     *             not positive.
+     * @return a pool that adaptively decreases its size when idle objects are
+     *         no longer needed.
+     * @see #erodingPool(ObjectPool)
+     */
+    public static <T> ObjectPool<T> erodingPool(final ObjectPool<T> pool,
+            final float factor) {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
+        }
+        if (factor <= 0f) {
+            throw new IllegalArgumentException(MSG_FACTOR_NEGATIVE);
         }
+        return new ErodingObjectPool<>(pool, factor);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void close() {
-            try {
-                keyedPool.close();
-            } catch (final Exception e) {
-                // swallowed
-            }
-        }
+    /**
+     * Gets the {@code Timer} for checking keyedPool's idle count.
+     *
+     * @return the {@link Timer} for checking keyedPool's idle count.
+     */
+    private static Timer getMinIdleTimer() {
+        return TimerHolder.MIN_IDLE_TIMER;
+    }
 
-        /**
-         * Returns the underlying pool
-         *
-         * @return the keyed pool that this ErodingKeyedObjectPool wraps
-         */
-        protected KeyedObjectPool<K, V> getKeyedPool() {
-            return keyedPool;
+    /**
+     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} with
+     * each key in {@code keys} for {@code count} number of times. This has
+     * the same effect as calling {@link #prefill(KeyedObjectPool, Object, int)}
+     * for each key in the {@code keys} collection.
+     *
+     * @param keyedPool
+     *            the keyedPool to prefill.
+     * @param keys
+     *            {@link Collection} of keys to add objects for.
+     * @param count
+     *            the number of idle objects to add for each {@code key}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws Exception
+     *             when {@link KeyedObjectPool#addObject(Object)} fails.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool}, {@code keys}, or any value in
+     *             {@code keys} is {@code null}.
+     * @see #prefill(KeyedObjectPool, Object, int)
+     * @deprecated Use {@link KeyedObjectPool#addObjects(Collection, int)}.
+     */
+    @Deprecated
+    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
+            final Collection<K> keys, final int count) throws Exception,
+            IllegalArgumentException {
+        if (keys == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYS);
         }
+        keyedPool.addObjects(keys, count);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public String toString() {
-            return "ErodingKeyedObjectPool{" + "factor=" +
-                    erodingFactor + ", keyedPool=" + keyedPool + '}';
+    /**
+     * Calls {@link KeyedObjectPool#addObject(Object)} on {@code keyedPool} with
+     * {@code key} {@code count} number of times.
+     *
+     * @param keyedPool
+     *            the keyedPool to prefill.
+     * @param key
+     *            the key to add objects for.
+     * @param count
+     *            the number of idle objects to add for {@code key}.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @throws Exception
+     *             when {@link KeyedObjectPool#addObject(Object)} fails.
+     * @throws IllegalArgumentException
+     *             when {@code keyedPool} or {@code key} is {@code null}.
+     * @deprecated Use {@link KeyedObjectPool#addObjects(Object, int)}.
+     */
+    @Deprecated
+    public static <K, V> void prefill(final KeyedObjectPool<K, V> keyedPool,
+            final K key, final int count) throws Exception,
+            IllegalArgumentException {
+        if (keyedPool == null) {
+            throw new IllegalArgumentException(MSG_NULL_KEYED_POOL);
         }
+        keyedPool.addObjects(key, count);
     }
 
     /**
-     * Extends ErodingKeyedObjectPool to allow erosion to take place on a
-     * per-key basis. Timing of erosion events is tracked separately for
-     * separate keyed pools.
+     * Calls {@link ObjectPool#addObject()} on {@code pool} {@code count} number
+     * of times.
      *
-     * @param <K> object pool key type
-     * @param <V> object pool value type
+     * @param pool
+     *            the pool to prefill.
+     * @param count
+     *            the number of idle objects to add.
+     * @param <T> the type of objects in the pool
+     * @throws Exception
+     *             when {@link ObjectPool#addObject()} fails.
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @deprecated Use {@link ObjectPool#addObjects(int)}.
      */
-    private static final class ErodingPerKeyKeyedObjectPool<K, V> extends
-            ErodingKeyedObjectPool<K, V> {
-
-        /** Erosion factor - same for all pools */
-        private final float factor;
+    @Deprecated
+    public static <T> void prefill(final ObjectPool<T> pool, final int count)
+            throws Exception, IllegalArgumentException {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
+        }
+        pool.addObjects(count);
+    }
 
-        /** Map of ErodingFactor instances keyed on pool keys */
-        private final Map<K, ErodingFactor> factors = Collections.synchronizedMap(new HashMap<K, ErodingFactor>());
+    /**
+     * Returns a synchronized (thread-safe) KeyedPooledObjectFactory backed by
+     * the specified KeyedPoolableObjectFactory.
+     *
+     * @param keyedFactory
+     *            the KeyedPooledObjectFactory to be "wrapped" in a
+     *            synchronized KeyedPooledObjectFactory.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a synchronized view of the specified KeyedPooledObjectFactory.
+     */
+    public static <K, V> KeyedPooledObjectFactory<K, V> synchronizedKeyedPooledFactory(
+            final KeyedPooledObjectFactory<K, V> keyedFactory) {
+        return new SynchronizedKeyedPooledObjectFactory<>(keyedFactory);
+    }
 
-        /**
-         * Creates a new ErordingPerKeyKeyedObjectPool decorating the given keyed
-         * pool with the specified erosion factor.
-         *
-         * @param keyedPool
-         *            underlying keyed pool
-         * @param factor
-         *            erosion factor
+    /**
+     * Returns a synchronized (thread-safe) KeyedObjectPool backed by the
+     * specified KeyedObjectPool.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
+     * </p>
+     *
+     * @param keyedPool
+     *            the KeyedObjectPool to be "wrapped" in a synchronized
+     *            KeyedObjectPool.
+     * @param <K> the type of the pool key
+     * @param <V> the type of pool entries
+     * @return a synchronized view of the specified KeyedObjectPool.
+     */
+    public static <K, V> KeyedObjectPool<K, V> synchronizedPool(
+            final KeyedObjectPool<K, V> keyedPool) {
+        /*
+         * assert !(keyedPool instanceof GenericKeyedObjectPool) :
+         * "GenericKeyedObjectPool is already thread-safe"; assert !(keyedPool
+         * instanceof StackKeyedObjectPool) :
+         * "StackKeyedObjectPool is already thread-safe"; assert
+         * !"org.apache.commons.pool.composite.CompositeKeyedObjectPool"
+         * .equals(keyedPool.getClass().getName()) :
+         * "CompositeKeyedObjectPools are already thread-safe";
          */
-        public ErodingPerKeyKeyedObjectPool(
-                final KeyedObjectPool<K, V> keyedPool, final float factor) {
-            super(keyedPool, null);
-            this.factor = factor;
-        }
+        return new SynchronizedKeyedObjectPool<>(keyedPool);
+    }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        protected ErodingFactor getErodingFactor(final K key) {
-            ErodingFactor eFactor = factors.get(key);
-            // this may result in two ErodingFactors being created for a key
-            // since they are small and cheap this is okay.
-            if (eFactor == null) {
-                eFactor = new ErodingFactor(this.factor);
-                factors.put(key, eFactor);
-            }
-            return eFactor;
+    /**
+     * Returns a synchronized (thread-safe) ObjectPool backed by the specified
+     * ObjectPool.
+     * <p>
+     * <b>Note:</b> This should not be used on pool implementations that already
+     * provide proper synchronization such as the pools provided in the Commons
+     * Pool library. Wrapping a pool that {@link #wait() waits} for poolable
+     * objects to be returned before allowing another one to be borrowed with
+     * another layer of synchronization will cause liveliness issues or a
+     * deadlock.
+     * </p>
+     *
+     * @param pool
+     *            the ObjectPool to be "wrapped" in a synchronized ObjectPool.
+     * @param <T> the type of objects in the pool
+     * @throws IllegalArgumentException
+     *             when {@code pool} is {@code null}.
+     * @return a synchronized view of the specified ObjectPool.
+     */
+    public static <T> ObjectPool<T> synchronizedPool(final ObjectPool<T> pool) {
+        if (pool == null) {
+            throw new IllegalArgumentException(MSG_NULL_POOL);
         }
 
-        /**
-         * {@inheritDoc}
+        /*
+         * assert !(pool instanceof GenericObjectPool) :
+         * "GenericObjectPool is already thread-safe"; assert !(pool instanceof
+         * SoftReferenceObjectPool) :
+         * "SoftReferenceObjectPool is already thread-safe"; assert !(pool
+         * instanceof StackObjectPool) :
+         * "StackObjectPool is already thread-safe"; assert
+         * !"org.apache.commons.pool.composite.CompositeObjectPool"
+         * .equals(pool.getClass().getName()) :
+         * "CompositeObjectPools are already thread-safe";
          */
-        @Override
-        public String toString() {
-            return "ErodingPerKeyKeyedObjectPool{" + "factor=" + factor +
-                    ", keyedPool=" + getKeyedPool() + '}';
-        }
+        return new SynchronizedObjectPool<>(pool);
+    }
+
+    /**
+     * Returns a synchronized (thread-safe) PooledObjectFactory backed by the
+     * specified PooledObjectFactory.
+     *
+     * @param factory
+     *            the PooledObjectFactory to be "wrapped" in a synchronized
+     *            PooledObjectFactory.
+     * @param <T> the type of objects in the pool
+     * @return a synchronized view of the specified PooledObjectFactory.
+     */
+    public static <T> PooledObjectFactory<T> synchronizedPooledFactory(
+            final PooledObjectFactory<T> factory) {
+        return new SynchronizedPooledObjectFactory<>(factory);
+    }
+
+    /**
+     * PoolUtils instances should NOT be constructed in standard programming.
+     * Instead, the class should be used procedurally: PoolUtils.adapt(aPool);.
+     * This constructor is public to permit tools that require a JavaBean
+     * instance to operate.
+     */
+    public PoolUtils() {
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/PooledObject.java b/java/org/apache/tomcat/dbcp/pool2/PooledObject.java
index 8901531..69caef2 100644
--- a/java/org/apache/tomcat/dbcp/pool2/PooledObject.java
+++ b/java/org/apache/tomcat/dbcp/pool2/PooledObject.java
@@ -17,6 +17,8 @@
 package org.apache.tomcat.dbcp.pool2;
 
 import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Deque;
 
 /**
@@ -33,34 +35,93 @@ import java.util.Deque;
 public interface PooledObject<T> extends Comparable<PooledObject<T>> {
 
     /**
-     * Obtains the underlying object that is wrapped by this instance of
-     * {@link PooledObject}.
+     * Allocates the object.
      *
-     * @return The wrapped object
+     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
      */
-    T getObject();
+    boolean allocate();
 
     /**
-     * Obtains the time (using the same basis as
-     * {@link System#currentTimeMillis()}) that this object was created.
+     * Orders instances based on idle time - i.e. the length of time since the
+     * instance was returned to the pool. Used by the GKOP idle object evictor.
+     *<p>
+     * Note: This class has a natural ordering that is inconsistent with
+     *       equals if distinct objects have the same identity hash code.
+     * </p>
+     * <p>
+     * {@inheritDoc}
+     * </p>
+     */
+    @Override
+    int compareTo(PooledObject<T> other);
+
+    /**
+     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
+     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
      *
-     * @return The creation time for the wrapped object
+     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}.
      */
-    long getCreateTime();
+    boolean deallocate();
+
+    /**
+     * Notifies the object that the eviction test has ended.
+     *
+     * @param idleQueue The queue of idle objects to which the object should be
+     *                  returned.
+     *
+     * @return  Currently not used.
+     */
+    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
+
+    @Override
+    boolean equals(Object obj);
+
+    /**
+     * Gets the amount of time this object last spent in the active state (it may still be active in which case
+     * subsequent calls will return an increased value).
+     *
+     * @return The duration last spent in the active state.
+     * @since 2.11.0
+     */
+    default Duration getActiveDuration() {
+        // Take copies to avoid threading issues
+        final Instant lastReturnInstant = getLastReturnInstant();
+        final Instant lastBorrowInstant = getLastBorrowInstant();
+        // @formatter:off
+        return lastReturnInstant.isAfter(lastBorrowInstant) ?
+                Duration.between(lastBorrowInstant, lastReturnInstant) :
+                Duration.between(lastBorrowInstant, Instant.now());
+        // @formatter:on
+    }
+
+    /**
+     * Gets the amount of time this object last spent in the active state (it may still be active in which case
+     * subsequent calls will return an increased value).
+     *
+     * @return The duration last spent in the active state.
+     * @since 2.10.0
+     * @deprecated Use {@link #getActiveDuration()}.
+     */
+    @Deprecated
+    default Duration getActiveTime() {
+        return getActiveDuration();
+    }
 
     /**
-     * Obtains the time in milliseconds that this object last spent in the
+     * Gets the amount of time in milliseconds this object last spent in the
      * active state (it may still be active in which case subsequent calls will
      * return an increased value).
      *
-     * @return The time in milliseconds last spent in the active state
+     * @return The time in milliseconds last spent in the active state.
+     * @deprecated Use {@link #getActiveTime()} which offers the best precision.
      */
+    @Deprecated
     long getActiveTimeMillis();
 
     /**
      * Gets the number of times this object has been borrowed.
      *
-     * @return -1 by default for old implementations prior to release 2.7.0.
+     * @return -1 by default for implementations prior to release 2.7.0.
      * @since 2.7.0
      */
     default long getBorrowedCount() {
@@ -68,124 +129,192 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     }
 
     /**
-     * Obtains the time in milliseconds that this object last spend in the
-     * idle state (it may still be idle in which case subsequent calls will
-     * return an increased value).
+     * Gets the time (using the same basis as {@link Instant#now()}) that this object was created.
      *
-     * @return The time in milliseconds last spent in the idle state
+     * @return The creation time for the wrapped object.
+     * @since 2.11.0
      */
-    long getIdleTimeMillis();
+    default Instant getCreateInstant() {
+        return Instant.ofEpochMilli(getCreateTime());
+    }
 
     /**
-     * Obtains the time the wrapped object was last borrowed.
+     * Gets the time (using the same basis as
+     * {@link System#currentTimeMillis()}) that this object was created.
      *
-     * @return The time the object was last borrowed
+     * @return The creation time for the wrapped object.
+     * @deprecated Use {@link #getCreateInstant()} which offers the best precision.
      */
-    long getLastBorrowTime();
+    @Deprecated
+    long getCreateTime();
 
     /**
-     * Obtains the time the wrapped object was last returned.
+     * Computes the duration since this object was created (using {@link Instant#now()}).
      *
-     * @return The time the object was last returned
+     * @return The duration since this object was created.
+     * @since 2.12.0
      */
-    long getLastReturnTime();
+    default Duration getFullDuration() {
+        return Duration.between(getCreateInstant(), Instant.now());
+    }
 
     /**
-     * Returns an estimate of the last time this object was used.  If the class
-     * of the pooled object implements {@link TrackedUse}, what is returned is
-     * the maximum of {@link TrackedUse#getLastUsed()} and
-     * {@link #getLastBorrowTime()}; otherwise this method gives the same
-     * value as {@link #getLastBorrowTime()}.
+     * Gets the amount of time that this object last spend in the
+     * idle state (it may still be idle in which case subsequent calls will
+     * return an increased value).
      *
-     * @return the last time this object was used
+     * @return The amount of time in last spent in the idle state.
+     * @since 2.11.0
      */
-    long getLastUsedTime();
+    default Duration getIdleDuration() {
+        return Duration.ofMillis(getIdleTimeMillis());
+    }
 
     /**
-     * Orders instances based on idle time - i.e. the length of time since the
-     * instance was returned to the pool. Used by the GKOP idle object evictor.
-     *<p>
-     * Note: This class has a natural ordering that is inconsistent with
-     *       equals if distinct objects have the same identity hash code.
-     * </p>
-     * <p>
-     * {@inheritDoc}
-     * </p>
+     * Gets the amount of time that this object last spend in the
+     * idle state (it may still be idle in which case subsequent calls will
+     * return an increased value).
+     *
+     * @return The amount of time in last spent in the idle state.
+     * @since 2.10.0
+     * @deprecated Use {@link #getIdleDuration()}.
      */
-    @Override
-    int compareTo(PooledObject<T> other);
+    @Deprecated
+    default Duration getIdleTime() {
+        return Duration.ofMillis(getIdleTimeMillis());
+    }
 
-    @Override
-    boolean equals(Object obj);
+    /**
+     * Gets the amount of time in milliseconds that this object last spend in the
+     * idle state (it may still be idle in which case subsequent calls will
+     * return an increased value).
+     *
+     * @return The time in milliseconds last spent in the idle state.
+     * @deprecated Use {@link #getIdleTime()} which offers the best precision.
+     */
+    @Deprecated
+    long getIdleTimeMillis();
 
-    @Override
-    int hashCode();
+    /**
+     * Gets the time the wrapped object was last borrowed.
+     *
+     * @return The time the object was last borrowed.
+     * @since 2.11.0
+     */
+    default Instant getLastBorrowInstant() {
+        return Instant.ofEpochMilli(getLastBorrowTime());
+    }
 
     /**
-     * Provides a String form of the wrapper for debug purposes. The format is
-     * not fixed and may change at any time.
-     * <p>
-     * {@inheritDoc}
+     * Gets the time the wrapped object was last borrowed.
+     *
+     * @return The time the object was last borrowed.
+     * @deprecated Use {@link #getLastBorrowInstant()} which offers the best precision.
      */
-    @Override
-    String toString();
+    @Deprecated
+    long getLastBorrowTime();
 
     /**
-     * Attempts to place the pooled object in the
-     * {@link PooledObjectState#EVICTION} state.
+     * Gets the time the wrapped object was last borrowed.
      *
-     * @return {@code true} if the object was placed in the
-     *         {@link PooledObjectState#EVICTION} state otherwise
-     *         {@code false}
+     * @return The time the object was last borrowed.
+     * @since 2.11.0
      */
-    boolean startEvictionTest();
+    default Instant getLastReturnInstant() {
+        return Instant.ofEpochMilli(getLastReturnTime());
+    }
 
     /**
-     * Called to inform the object that the eviction test has ended.
+     * Gets the time the wrapped object was last returned.
      *
-     * @param idleQueue The queue of idle objects to which the object should be
-     *                  returned
+     * @return The time the object was last returned.
+     * @deprecated Use {@link #getLastReturnInstant()} which offers the best precision.
+     */
+    @Deprecated
+    long getLastReturnTime();
+
+    /**
+     * Gets an estimate of the last time this object was used. If the class of the pooled object implements
+     * {@link TrackedUse}, what is returned is the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same value as {@link #getLastBorrowTime()}.
      *
-     * @return  Currently not used
+     * @return the last time this object was used
+     * @since 2.11.0
      */
-    boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
+    default Instant getLastUsedInstant() {
+        return Instant.ofEpochMilli(getLastUsedTime());
+    }
 
     /**
-     * Allocates the object.
+     * Gets an estimate of the last time this object was used.  If the class
+     * of the pooled object implements {@link TrackedUse}, what is returned is
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same
+     * value as {@link #getLastBorrowTime()}.
      *
-     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
+     * @return the last time this object was used.
+     * @deprecated Use {@link #getLastUsedInstant()} which offers the best precision.
      */
-    boolean allocate();
+    @Deprecated
+    long getLastUsedTime();
 
     /**
-     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
-     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
+     * Gets the underlying object that is wrapped by this instance of
+     * {@link PooledObject}.
      *
-     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
+     * @return The wrapped object.
      */
-    boolean deallocate();
+    T getObject();
 
     /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}
+     * Gets the state of this object.
+     * @return state
+     */
+    PooledObjectState getState();
+
+    @Override
+    int hashCode();
+
+    /**
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
      */
     void invalidate();
 
     /**
-     * Is abandoned object tracking being used? If this is true the
+     * Marks the pooled object as abandoned.
+     */
+    void markAbandoned();
+
+    /**
+     * Marks the object as returning to the pool.
+     */
+    void markReturning();
+
+    /**
+     * Prints the stack trace of the code that borrowed this pooled object and
+     * the stack trace of the last code to use this object (if available) to
+     * the supplied writer.
+     *
+     * @param   writer  The destination for the debug output.
+     */
+    void printStackTrace(PrintWriter writer);
+
+    /**
+     * Sets whether to use abandoned object tracking. If this is true the
      * implementation will need to record the stack trace of the last caller to
      * borrow this object.
      *
      * @param   logAbandoned    The new configuration setting for abandoned
-     *                          object tracking
+     *                          object tracking.
      */
     void setLogAbandoned(boolean logAbandoned);
 
     /**
-     * Configures the stack trace generation strategy based on whether or not fully detailed stack traces are required.
+     * Sets the stack trace generation strategy based on whether or not fully detailed stack traces are required.
      * When set to false, abandoned logs may only include caller class information rather than method names, line
      * numbers, and other normal metadata available in a full stack trace.
      *
-     * @param requireFullStackTrace the new configuration setting for abandoned object logging
+     * @param requireFullStackTrace the new configuration setting for abandoned object logging.
      * @since 2.7.0
      */
     default void setRequireFullStackTrace(final boolean requireFullStackTrace) {
@@ -193,32 +322,27 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     }
 
     /**
-     * Record the current stack trace as the last time the object was used.
-     */
-    void use();
-
-    /**
-     * Prints the stack trace of the code that borrowed this pooled object and
-     * the stack trace of the last code to use this object (if available) to
-     * the supplied writer.
+     * Attempts to place the pooled object in the
+     * {@link PooledObjectState#EVICTION} state.
      *
-     * @param   writer  The destination for the debug output
+     * @return {@code true} if the object was placed in the
+     *         {@link PooledObjectState#EVICTION} state otherwise
+     *         {@code false}.
      */
-    void printStackTrace(PrintWriter writer);
+    boolean startEvictionTest();
 
     /**
-     * Returns the state of this object.
-     * @return state
+     * Gets a String form of the wrapper for debug purposes. The format is
+     * not fixed and may change at any time.
+     *
+     * {@inheritDoc}
      */
-    PooledObjectState getState();
+    @Override
+    String toString();
 
     /**
-     * Marks the pooled object as abandoned.
+     * Records the current stack trace as the last time the object was used.
      */
-    void markAbandoned();
+    void use();
 
-    /**
-     * Marks the object as returning to the pool.
-     */
-    void markReturning();
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/PooledObjectFactory.java b/java/org/apache/tomcat/dbcp/pool2/PooledObjectFactory.java
index 9c95d0d..59a6f74 100644
--- a/java/org/apache/tomcat/dbcp/pool2/PooledObjectFactory.java
+++ b/java/org/apache/tomcat/dbcp/pool2/PooledObjectFactory.java
@@ -73,15 +73,16 @@ package org.apache.tomcat.dbcp.pool2;
 public interface PooledObjectFactory<T> {
 
   /**
-   * Creates an instance that can be served by the pool and wrap it in a
-   * {@link PooledObject} to be managed by the pool.
+   * Reinitializes an instance to be returned by the pool.
    *
-   * @return a {@code PooledObject} wrapping an instance that can be served by the pool
+   * @param p a {@code PooledObject} wrapping the instance to be activated
    *
-   * @throws Exception if there is a problem creating a new instance,
-   *    this will be propagated to the code requesting an object.
+   * @throws Exception if there is a problem activating {@code obj},
+   *    this exception may be swallowed by the pool.
+   *
+   * @see #destroyObject
    */
-  PooledObject<T> makeObject() throws Exception;
+  void activateObject(PooledObject<T> p) throws Exception;
 
   /**
    * Destroys an instance no longer needed by the pool, using the default (NORMAL)
@@ -111,7 +112,7 @@ public interface PooledObjectFactory<T> {
    * DestroyMode.
    *
    * @param p a {@code PooledObject} wrapping the instance to be destroyed
-   * @param mode DestroyMode providing context to the factory
+   * @param destroyMode DestroyMode providing context to the factory
    *
    * @throws Exception should be avoided as it may be swallowed by
    *    the pool implementation.
@@ -122,41 +123,40 @@ public interface PooledObjectFactory<T> {
    * @see DestroyMode
    * @since 2.9.0
    */
-  default void destroyObject(final PooledObject<T> p, final DestroyMode mode) throws Exception {
+  default void destroyObject(final PooledObject<T> p, final DestroyMode destroyMode) throws Exception {
       destroyObject(p);
   }
 
   /**
-   * Ensures that the instance is safe to be returned by the pool.
+   * Creates an instance that can be served by the pool and wrap it in a
+   * {@link PooledObject} to be managed by the pool.
    *
-   * @param p a {@code PooledObject} wrapping the instance to be validated
+   * @return a {@code PooledObject} wrapping an instance that can be served by the pool
    *
-   * @return {@code false} if {@code obj} is not valid and should
-   *         be dropped from the pool, {@code true} otherwise.
+   * @throws Exception if there is a problem creating a new instance,
+   *    this will be propagated to the code requesting an object.
    */
-  boolean validateObject(PooledObject<T> p);
+  PooledObject<T> makeObject() throws Exception;
 
   /**
-   * Reinitializes an instance to be returned by the pool.
+   * Uninitializes an instance to be returned to the idle object pool.
    *
-   * @param p a {@code PooledObject} wrapping the instance to be activated
+   * @param p a {@code PooledObject} wrapping the instance to be passivated
    *
-   * @throws Exception if there is a problem activating {@code obj},
+   * @throws Exception if there is a problem passivating {@code obj},
    *    this exception may be swallowed by the pool.
    *
    * @see #destroyObject
    */
-  void activateObject(PooledObject<T> p) throws Exception;
+  void passivateObject(PooledObject<T> p) throws Exception;
 
   /**
-   * Uninitializes an instance to be returned to the idle object pool.
-   *
-   * @param p a {@code PooledObject} wrapping the instance to be passivated
+   * Ensures that the instance is safe to be returned by the pool.
    *
-   * @throws Exception if there is a problem passivating {@code obj},
-   *    this exception may be swallowed by the pool.
+   * @param p a {@code PooledObject} wrapping the instance to be validated
    *
-   * @see #destroyObject
+   * @return {@code false} if {@code obj} is not valid and should
+   *         be dropped from the pool, {@code true} otherwise.
    */
-  void passivateObject(PooledObject<T> p) throws Exception;
+  boolean validateObject(PooledObject<T> p);
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/PooledObjectState.java b/java/org/apache/tomcat/dbcp/pool2/PooledObjectState.java
index fa522fd..b405f94 100644
--- a/java/org/apache/tomcat/dbcp/pool2/PooledObjectState.java
+++ b/java/org/apache/tomcat/dbcp/pool2/PooledObjectState.java
@@ -17,7 +17,7 @@
 package org.apache.tomcat.dbcp.pool2;
 
 /**
- * Provides the possible states that a {@link PooledObject} may be in.
+ * Provides all possible states of a {@link PooledObject}.
  *
  * @since 2.0
  */
@@ -39,12 +39,12 @@ public enum PooledObjectState {
     EVICTION,
 
     /**
-     * Not in the queue, currently being tested for possible eviction. An
-     * attempt to borrow the object was made while being tested which removed it
-     * from the queue. It should be returned to the head of the queue once
-     * eviction testing completes.
-     * TODO: Consider allocating object and ignoring the result of the eviction
-     *       test.
+     * Not in the queue, currently being tested for possible eviction. An attempt to borrow the object was made while
+     * being tested which removed it from the queue. It should be returned to the head of the queue once eviction
+     * testing completes.
+     * <p>
+     * TODO: Consider allocating object and ignoring the result of the eviction test.
+     * </p>
      */
     EVICTION_RETURN_TO_HEAD,
 
@@ -54,24 +54,20 @@ public enum PooledObjectState {
     VALIDATION,
 
     /**
-     * Not in queue, currently being validated. The object was borrowed while
-     * being validated and since testOnBorrow was configured, it was removed
-     * from the queue and pre-allocated. It should be allocated once validation
-     * completes.
+     * Not in queue, currently being validated. The object was borrowed while being validated and since testOnBorrow was
+     * configured, it was removed from the queue and pre-allocated. It should be allocated once validation completes.
      */
     VALIDATION_PREALLOCATED,
 
     /**
-     * Not in queue, currently being validated. An attempt to borrow the object
-     * was made while previously being tested for eviction which removed it from
-     * the queue. It should be returned to the head of the queue once validation
+     * Not in queue, currently being validated. An attempt to borrow the object was made while previously being tested
+     * for eviction which removed it from the queue. It should be returned to the head of the queue once validation
      * completes.
      */
     VALIDATION_RETURN_TO_HEAD,
 
     /**
-     * Failed maintenance (e.g. eviction test or validation) and will be / has
-     * been destroyed
+     * Failed maintenance (e.g. eviction test or validation) and will be / has been destroyed
      */
     INVALID,
 
diff --git a/java/org/apache/tomcat/dbcp/pool2/SwallowedExceptionListener.java b/java/org/apache/tomcat/dbcp/pool2/SwallowedExceptionListener.java
index 53995c1..c80b05b 100644
--- a/java/org/apache/tomcat/dbcp/pool2/SwallowedExceptionListener.java
+++ b/java/org/apache/tomcat/dbcp/pool2/SwallowedExceptionListener.java
@@ -27,7 +27,7 @@ package org.apache.tomcat.dbcp.pool2;
 public interface SwallowedExceptionListener {
 
     /**
-     * This method is called every time the implementation unavoidably swallows
+     * Notifies this instance every time the implementation unavoidably swallows
      * an exception.
      *
      * @param e The exception that was swallowed
diff --git a/java/org/apache/tomcat/dbcp/pool2/TrackedUse.java b/java/org/apache/tomcat/dbcp/pool2/TrackedUse.java
index 01349cb..cac0ce2 100644
--- a/java/org/apache/tomcat/dbcp/pool2/TrackedUse.java
+++ b/java/org/apache/tomcat/dbcp/pool2/TrackedUse.java
@@ -16,21 +16,37 @@
  */
 package org.apache.tomcat.dbcp.pool2;
 
+import java.time.Instant;
+
 /**
- * This interface allows pooled objects to make information available about when
- * and how they were used available to the object pool. The object pool may, but
- * is not required, to use this information to make more informed decisions when
- * determining the state of a pooled object - for instance whether or not the
- * object has been abandoned.
+ * Allows pooled objects to make information available about when and how they were used available to the object pool.
+ * The object pool may, but is not required, to use this information to make more informed decisions when determining
+ * the state of a pooled object - for instance whether or not the object has been abandoned.
  *
  * @since 2.0
  */
 public interface TrackedUse {
 
     /**
-     * Get the last time this object was used in ms.
+     * Gets the last time this object was used in milliseconds.
      *
-     * @return long time in ms
+     * @return the last time this object was used in milliseconds.
+     * @deprecated Use {@link #getLastUsedInstant()} which offers the best precision.
      */
+    @Deprecated
     long getLastUsed();
+
+    /**
+     * Gets the last Instant this object was used.
+     * <p>
+     * Starting with Java 9, the JRE {@code SystemClock} precision is increased usually down to microseconds, or tenth
+     * of microseconds, depending on the OS, Hardware, and JVM implementation.
+     * </p>
+     *
+     * @return the last Instant this object was used.
+     * @since 2.11.0
+     */
+    default Instant getLastUsedInstant() {
+        return Instant.ofEpochMilli(getLastUsed());
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/UsageTracking.java b/java/org/apache/tomcat/dbcp/pool2/UsageTracking.java
index afd33fd..9d49863 100644
--- a/java/org/apache/tomcat/dbcp/pool2/UsageTracking.java
+++ b/java/org/apache/tomcat/dbcp/pool2/UsageTracking.java
@@ -17,23 +17,20 @@
 package org.apache.tomcat.dbcp.pool2;
 
 /**
- * This interface may be implemented by an object pool to enable clients
- * (primarily those clients that wrap pools to provide pools with extended
- * features) to provide additional information to the pool relating to object
- * using allowing more informed decisions and reporting to be made regarding
- * abandoned objects.
+ * This interface may be implemented by an object pool to enable clients (primarily those clients that wrap pools to
+ * provide pools with extended features) to provide additional information to the pool relating to object using allowing
+ * more informed decisions and reporting to be made regarding abandoned objects.
  *
- * @param <T>   The type of object provided by the pool.
+ * @param <T> The type of object provided by the pool.
  *
  * @since 2.0
  */
 public interface UsageTracking<T> {
 
     /**
-     * This method is called every time a pooled object is used to enable the pool to
-     * better track borrowed objects.
+     * Called every time a pooled object is used to enable the pool to better track borrowed objects.
      *
-     * @param pooledObject  The object that is being used
+     * @param pooledObject The object that is being used.
      */
     void use(T pooledObject);
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/AbandonedConfig.java b/java/org/apache/tomcat/dbcp/pool2/impl/AbandonedConfig.java
index 395d2bbd..e3d1f67 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/AbandonedConfig.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/AbandonedConfig.java
@@ -19,6 +19,10 @@ package org.apache.tomcat.dbcp.pool2.impl;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
+import java.time.Duration;
+
+import org.apache.tomcat.dbcp.pool2.TrackedUse;
+import org.apache.tomcat.dbcp.pool2.UsageTracking;
 
 /**
  * Configuration settings for abandoned object removal.
@@ -28,117 +32,86 @@ import java.nio.charset.Charset;
 public class AbandonedConfig {
 
     /**
-     * Whether or not borrowObject performs abandoned object removal.
+     * The 5 minutes Duration.
      */
-    private boolean removeAbandonedOnBorrow = false;
+    private static final Duration DEFAULT_REMOVE_ABANDONED_TIMEOUT_DURATION = Duration.ofMinutes(5);
 
     /**
-     * <p>Flag to remove abandoned objects if they exceed the
-     * removeAbandonedTimeout when borrowObject is invoked.</p>
+     * Creates a new instance with values from the given instance.
      *
-     * <p>The default value is false.</p>
-     *
-     * <p>If set to true, abandoned objects are removed by borrowObject if
-     * there are fewer than 2 idle objects available in the pool and
-     * {@code getNumActive() &gt; getMaxTotal() - 3}</p>
-     *
-     * @return true if abandoned objects are to be removed by borrowObject
+     * @param abandonedConfig the source, may be null.
+     * @return A new instance or null if the input is null.
+     * @since 2.11.0
      */
-    public boolean getRemoveAbandonedOnBorrow() {
-        return this.removeAbandonedOnBorrow;
+    public static AbandonedConfig copy(final AbandonedConfig abandonedConfig) {
+        return abandonedConfig == null ? null : new AbandonedConfig(abandonedConfig);
     }
 
     /**
-     * <p>Flag to remove abandoned objects if they exceed the
-     * removeAbandonedTimeout when borrowObject is invoked.</p>
-     *
-     * @param removeAbandonedOnBorrow true means abandoned objects will be
-     *   removed by borrowObject
-     * @see #getRemoveAbandonedOnBorrow()
+     * Whether or not borrowObject performs abandoned object removal.
      */
-    public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) {
-        this.removeAbandonedOnBorrow = removeAbandonedOnBorrow;
-    }
+    private boolean removeAbandonedOnBorrow;
 
     /**
      * Whether or not pool maintenance (evictor) performs abandoned object
      * removal.
      */
-    private boolean removeAbandonedOnMaintenance = false;
+    private boolean removeAbandonedOnMaintenance;
 
     /**
-     * <p>Flag to remove abandoned objects if they exceed the
-     * removeAbandonedTimeout when pool maintenance (the "evictor")
-     * runs.</p>
-     *
-     * <p>The default value is false.</p>
-     *
-     * <p>If set to true, abandoned objects are removed by the pool
-     * maintenance thread when it runs.  This setting has no effect
-     * unless maintenance is enabled by setting
-     *{@link GenericObjectPool#getTimeBetweenEvictionRunsMillis() timeBetweenEvictionRunsMillis}
-     * to a positive number.</p>
-     *
-     * @return true if abandoned objects are to be removed by the evictor
+     * Timeout before an abandoned object can be removed.
      */
-    public boolean getRemoveAbandonedOnMaintenance() {
-        return this.removeAbandonedOnMaintenance;
-    }
+    private Duration removeAbandonedTimeoutDuration = DEFAULT_REMOVE_ABANDONED_TIMEOUT_DURATION;
 
     /**
-     * <p>Flag to remove abandoned objects if they exceed the
-     * removeAbandonedTimeout when pool maintenance runs.</p>
+     * Determines whether or not to log stack traces for application code
+     * which abandoned an object.
+     */
+    private boolean logAbandoned;
+
+    /**
+     * Determines whether or not to log full stack traces when logAbandoned is true.
+     * If disabled, then a faster method for logging stack traces with only class data
+     * may be used if possible.
      *
-     * @param removeAbandonedOnMaintenance true means abandoned objects will be
-     *   removed by pool maintenance
-     * @see #getRemoveAbandonedOnMaintenance
+     * @since 2.5
      */
-    public void setRemoveAbandonedOnMaintenance(final boolean removeAbandonedOnMaintenance) {
-        this.removeAbandonedOnMaintenance = removeAbandonedOnMaintenance;
-    }
+    private boolean requireFullStackTrace = true;
 
     /**
-     * Timeout in seconds before an abandoned object can be removed.
+     * PrintWriter to use to log information on abandoned objects.
+     * Use of default system encoding is deliberate.
      */
-    private int removeAbandonedTimeout = 300;
+    private PrintWriter logWriter = new PrintWriter(new OutputStreamWriter(System.out, Charset.defaultCharset()));
 
     /**
-     * <p>Timeout in seconds before an abandoned object can be removed.</p>
-     *
-     * <p>The time of most recent use of an object is the maximum (latest) of
-     * {@link org.apache.tomcat.dbcp.pool2.TrackedUse#getLastUsed()}
-     * (if this class of the object implements
-     * TrackedUse) and the time when the object was borrowed from the pool.</p>
-     *
-     * <p>The default value is 300 seconds.</p>
-     *
-     * @return the abandoned object timeout in seconds
+     * If the pool implements {@link UsageTracking}, should the pool record a
+     * stack trace every time a method is called on a pooled object and retain
+     * the most recent stack trace to aid debugging of abandoned objects?
      */
-    public int getRemoveAbandonedTimeout() {
-        return this.removeAbandonedTimeout;
-    }
+    private boolean useUsageTracking;
 
     /**
-     * <p>Sets the timeout in seconds before an abandoned object can be
-     * removed</p>
-     *
-     * <p>Setting this property has no effect if
-     * {@link #getRemoveAbandonedOnBorrow() removeAbandonedOnBorrow} and
-     * {@link #getRemoveAbandonedOnMaintenance() removeAbandonedOnMaintenance}
-     * are both false.</p>
-     *
-     * @param removeAbandonedTimeout new abandoned timeout in seconds
-     * @see #getRemoveAbandonedTimeout()
+     * Creates a new instance.
      */
-    public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) {
-        this.removeAbandonedTimeout = removeAbandonedTimeout;
+    public AbandonedConfig() {
+        // empty
     }
 
     /**
-     * Determines whether or not to log stack traces for application code
-     * which abandoned an object.
+     * Creates a new instance with values from the given instance.
+     *
+     * @param abandonedConfig the source.
      */
-    private boolean logAbandoned = false;
+    private AbandonedConfig(final AbandonedConfig abandonedConfig) {
+        this.setLogAbandoned(abandonedConfig.getLogAbandoned());
+        this.setLogWriter(abandonedConfig.getLogWriter());
+        this.setRemoveAbandonedOnBorrow(abandonedConfig.getRemoveAbandonedOnBorrow());
+        this.setRemoveAbandonedOnMaintenance(abandonedConfig.getRemoveAbandonedOnMaintenance());
+        this.setRemoveAbandonedTimeout(abandonedConfig.getRemoveAbandonedTimeoutDuration());
+        this.setUseUsageTracking(abandonedConfig.getUseUsageTracking());
+        this.setRequireFullStackTrace(abandonedConfig.getRequireFullStackTrace());
+    }
 
     /**
      * Flag to log stack traces for application code which abandoned
@@ -157,25 +130,83 @@ public class AbandonedConfig {
     }
 
     /**
-     * Sets the flag to log stack traces for application code which abandoned
-     * an object.
+     * Gets the log writer being used by this configuration to log
+     * information on abandoned objects. If not set, a PrintWriter based on
+     * System.out with the system default encoding is used.
      *
-     * @param logAbandoned true turns on abandoned stack trace logging
-     * @see #getLogAbandoned()
+     * @return log writer in use
+     */
+    public PrintWriter getLogWriter() {
+        return logWriter;
+    }
+
+    /**
+     * <p>Flag to remove abandoned objects if they exceed the
+     * removeAbandonedTimeout when borrowObject is invoked.</p>
+     *
+     * <p>The default value is false.</p>
+     *
+     * <p>If set to true, abandoned objects are removed by borrowObject if
+     * there are fewer than 2 idle objects available in the pool and
+     * {@code getNumActive() &gt; getMaxTotal() - 3}</p>
      *
+     * @return true if abandoned objects are to be removed by borrowObject
      */
-    public void setLogAbandoned(final boolean logAbandoned) {
-        this.logAbandoned = logAbandoned;
+    public boolean getRemoveAbandonedOnBorrow() {
+        return this.removeAbandonedOnBorrow;
     }
 
     /**
-     * Determines whether or not to log full stack traces when logAbandoned is true.
-     * If disabled, then a faster method for logging stack traces with only class data
-     * may be used if possible.
+     * <p>Flag to remove abandoned objects if they exceed the
+     * removeAbandonedTimeout when pool maintenance (the "evictor")
+     * runs.</p>
      *
-     * @since 2.5
+     * <p>The default value is false.</p>
+     *
+     * <p>If set to true, abandoned objects are removed by the pool
+     * maintenance thread when it runs.  This setting has no effect
+     * unless maintenance is enabled by setting
+     *{@link GenericObjectPool#getDurationBetweenEvictionRuns() durationBetweenEvictionRuns}
+     * to a positive number.</p>
+     *
+     * @return true if abandoned objects are to be removed by the evictor
      */
-    private boolean requireFullStackTrace = true;
+    public boolean getRemoveAbandonedOnMaintenance() {
+        return this.removeAbandonedOnMaintenance;
+    }
+
+    /**
+     * <p>Timeout in seconds before an abandoned object can be removed.</p>
+     *
+     * <p>The time of most recent use of an object is the maximum (latest) of
+     * {@link TrackedUse#getLastUsedInstant()} (if this class of the object implements
+     * TrackedUse) and the time when the object was borrowed from the pool.</p>
+     *
+     * <p>The default value is 300 seconds.</p>
+     *
+     * @return the abandoned object timeout in seconds.
+     * @deprecated Use {@link #getRemoveAbandonedTimeoutDuration()}.
+     */
+    @Deprecated
+    public int getRemoveAbandonedTimeout() {
+        return (int) this.removeAbandonedTimeoutDuration.getSeconds();
+    }
+
+    /**
+     * <p>Timeout before an abandoned object can be removed.</p>
+     *
+     * <p>The time of most recent use of an object is the maximum (latest) of
+     * {@link TrackedUse#getLastUsedInstant()} (if this class of the object implements
+     * TrackedUse) and the time when the object was borrowed from the pool.</p>
+     *
+     * <p>The default value is 300 seconds.</p>
+     *
+     * @return the abandoned object timeout.
+     * @since 2.10.0
+     */
+    public Duration getRemoveAbandonedTimeoutDuration() {
+        return this.removeAbandonedTimeoutDuration;
+    }
 
     /**
      * Indicates if full stack traces are required when {@link #getLogAbandoned() logAbandoned}
@@ -193,33 +224,26 @@ public class AbandonedConfig {
     }
 
     /**
-     * Sets the flag to require full stack traces for logging abandoned connections when enabled.
+     * If the pool implements {@link UsageTracking}, should the pool record a
+     * stack trace every time a method is called on a pooled object and retain
+     * the most recent stack trace to aid debugging of abandoned objects?
      *
-     * @param requireFullStackTrace indicates whether or not full stack traces are required in
-     *                              abandoned connection logs
-     * @see CallStack
-     * @see #getRequireFullStackTrace()
-     * @since 2.5
+     * @return {@code true} if usage tracking is enabled
      */
-    public void setRequireFullStackTrace(final boolean requireFullStackTrace) {
-        this.requireFullStackTrace = requireFullStackTrace;
+    public boolean getUseUsageTracking() {
+        return useUsageTracking;
     }
 
     /**
-     * PrintWriter to use to log information on abandoned objects.
-     * Use of default system encoding is deliberate.
-     */
-    private PrintWriter logWriter = new PrintWriter(new OutputStreamWriter(System.out, Charset.defaultCharset()));
-
-    /**
-     * Returns the log writer being used by this configuration to log
-     * information on abandoned objects. If not set, a PrintWriter based on
-     * System.out with the system default encoding is used.
+     * Sets the flag to log stack traces for application code which abandoned
+     * an object.
+     *
+     * @param logAbandoned true turns on abandoned stack trace logging
+     * @see #getLogAbandoned()
      *
-     * @return log writer in use
      */
-    public PrintWriter getLogWriter() {
-        return logWriter;
+    public void setLogAbandoned(final boolean logAbandoned) {
+        this.logAbandoned = logAbandoned;
     }
 
     /**
@@ -233,30 +257,79 @@ public class AbandonedConfig {
     }
 
     /**
-     * If the pool implements
-     * {@link org.apache.tomcat.dbcp.pool2.UsageTracking}, should the pool
-     * record a stack trace every time a method is called on a pooled object and
-     * retain the most recent stack trace to aid debugging of abandoned objects?
+     * Flag to remove abandoned objects if they exceed the
+     * removeAbandonedTimeout when borrowObject is invoked.
+     *
+     * @param removeAbandonedOnBorrow true means abandoned objects will be
+     *   removed by borrowObject
+     * @see #getRemoveAbandonedOnBorrow()
      */
-    private boolean useUsageTracking = false;
+    public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) {
+        this.removeAbandonedOnBorrow = removeAbandonedOnBorrow;
+    }
 
     /**
-     * If the pool implements
-     * {@link org.apache.tomcat.dbcp.pool2.UsageTracking}, should the pool
-     * record a
-     * stack trace every time a method is called on a pooled object and retain
-     * the most recent stack trace to aid debugging of abandoned objects?
+     * Flag to remove abandoned objects if they exceed the
+     * removeAbandonedTimeout when pool maintenance runs.
      *
-     * @return {@code true} if usage tracking is enabled
+     * @param removeAbandonedOnMaintenance true means abandoned objects will be
+     *   removed by pool maintenance
+     * @see #getRemoveAbandonedOnMaintenance
      */
-    public boolean getUseUsageTracking() {
-        return useUsageTracking;
+    public void setRemoveAbandonedOnMaintenance(final boolean removeAbandonedOnMaintenance) {
+        this.removeAbandonedOnMaintenance = removeAbandonedOnMaintenance;
+    }
+
+    /**
+     * Sets the timeout before an abandoned object can be
+     * removed.
+     *
+     * <p>Setting this property has no effect if
+     * {@link #getRemoveAbandonedOnBorrow() removeAbandonedOnBorrow} and
+     * {@link #getRemoveAbandonedOnMaintenance() removeAbandonedOnMaintenance}
+     * are both false.</p>
+     *
+     * @param removeAbandonedTimeout new abandoned timeout
+     * @see #getRemoveAbandonedTimeoutDuration()
+     * @since 2.10.0
+     */
+    public void setRemoveAbandonedTimeout(final Duration removeAbandonedTimeout) {
+        this.removeAbandonedTimeoutDuration = PoolImplUtils.nonNull(removeAbandonedTimeout, DEFAULT_REMOVE_ABANDONED_TIMEOUT_DURATION);
+    }
+
+    /**
+     * Sets the timeout in seconds before an abandoned object can be
+     * removed.
+     *
+     * <p>Setting this property has no effect if
+     * {@link #getRemoveAbandonedOnBorrow() removeAbandonedOnBorrow} and
+     * {@link #getRemoveAbandonedOnMaintenance() removeAbandonedOnMaintenance}
+     * are both false.</p>
+     *
+     * @param removeAbandonedTimeoutSeconds new abandoned timeout in seconds
+     * @see #getRemoveAbandonedTimeoutDuration()
+     * @deprecated Use {@link #setRemoveAbandonedTimeout(Duration)}.
+     */
+    @Deprecated
+    public void setRemoveAbandonedTimeout(final int removeAbandonedTimeoutSeconds) {
+        setRemoveAbandonedTimeout(Duration.ofSeconds(removeAbandonedTimeoutSeconds));
+    }
+
+    /**
+     * Sets the flag to require full stack traces for logging abandoned connections when enabled.
+     *
+     * @param requireFullStackTrace indicates whether or not full stack traces are required in
+     *                              abandoned connection logs
+     * @see CallStack
+     * @see #getRequireFullStackTrace()
+     * @since 2.5
+     */
+    public void setRequireFullStackTrace(final boolean requireFullStackTrace) {
+        this.requireFullStackTrace = requireFullStackTrace;
     }
 
     /**
-     * If the pool implements
-     * {@link org.apache.tomcat.dbcp.pool2.UsageTracking}, configure whether the
-     * pool
+     * If the pool implements {@link UsageTracking}, configure whether the pool
      * should record a stack trace every time a method is called on a pooled
      * object and retain the most recent stack trace to aid debugging of
      * abandoned objects.
@@ -279,8 +352,8 @@ public class AbandonedConfig {
         builder.append(removeAbandonedOnBorrow);
         builder.append(", removeAbandonedOnMaintenance=");
         builder.append(removeAbandonedOnMaintenance);
-        builder.append(", removeAbandonedTimeout=");
-        builder.append(removeAbandonedTimeout);
+        builder.append(", removeAbandonedTimeoutDuration=");
+        builder.append(removeAbandonedTimeoutDuration);
         builder.append(", logAbandoned=");
         builder.append(logAbandoned);
         builder.append(", logWriter=");
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/BaseGenericObjectPool.java b/java/org/apache/tomcat/dbcp/pool2/impl/BaseGenericObjectPool.java
index 3583731..039340b 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/BaseGenericObjectPool.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/BaseGenericObjectPool.java
@@ -22,13 +22,19 @@ import java.io.Writer;
 import java.lang.management.ManagementFactory;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.TimerTask;
 import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 import javax.management.InstanceAlreadyExistsException;
 import javax.management.InstanceNotFoundException;
@@ -56,75 +62,329 @@ import org.apache.tomcat.dbcp.pool2.SwallowedExceptionListener;
  */
 public abstract class BaseGenericObjectPool<T> extends BaseObject {
 
+    /**
+     * The idle object eviction iterator. Holds a reference to the idle objects.
+     */
+    class EvictionIterator implements Iterator<PooledObject<T>> {
+
+        private final Deque<PooledObject<T>> idleObjects;
+        private final Iterator<PooledObject<T>> idleObjectIterator;
+
+        /**
+         * Constructs an EvictionIterator for the provided idle instance deque.
+         * @param idleObjects underlying deque.
+         */
+        EvictionIterator(final Deque<PooledObject<T>> idleObjects) {
+            this.idleObjects = idleObjects;
+
+            if (getLifo()) {
+                idleObjectIterator = idleObjects.descendingIterator();
+            } else {
+                idleObjectIterator = idleObjects.iterator();
+            }
+        }
+
+        /**
+         * Gets the idle object deque referenced by this iterator.
+         * @return the idle object deque
+         */
+        public Deque<PooledObject<T>> getIdleObjects() {
+            return idleObjects;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean hasNext() {
+            return idleObjectIterator.hasNext();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public PooledObject<T> next() {
+            return idleObjectIterator.next();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void remove() {
+            idleObjectIterator.remove();
+        }
+
+    }
+
+    /**
+     * The idle object evictor {@link TimerTask}.
+     *
+     * @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis
+     */
+    class Evictor implements Runnable {
+
+        private ScheduledFuture<?> scheduledFuture;
+
+        /**
+         * Cancels the scheduled future.
+         */
+        void cancel() {
+            scheduledFuture.cancel(false);
+        }
+
+
+        /**
+         * 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.
+         */
+        @Override
+        public void run() {
+            final ClassLoader savedClassLoader =
+                    Thread.currentThread().getContextClassLoader();
+            try {
+                if (factoryClassLoader != null) {
+                    // Set the class loader for the factory
+                    final ClassLoader cl = factoryClassLoader.get();
+                    if (cl == null) {
+                        // The pool has been dereferenced and the class loader
+                        // GC'd. Cancel this timer so the pool can be GC'd as
+                        // well.
+                        cancel();
+                        return;
+                    }
+                    Thread.currentThread().setContextClassLoader(cl);
+                }
+
+                // Evict from the pool
+                try {
+                    evict();
+                } catch(final Exception e) {
+                    swallowException(e);
+                } catch(final 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 {
+                    ensureMinIdle();
+                } catch (final Exception e) {
+                    swallowException(e);
+                }
+            } finally {
+                // Restore the previous CCL
+                Thread.currentThread().setContextClassLoader(savedClassLoader);
+            }
+        }
+
+
+        /**
+         * Sets the scheduled future.
+         *
+         * @param scheduledFuture the scheduled future.
+         */
+        void setScheduledFuture(final ScheduledFuture<?> scheduledFuture) {
+            this.scheduledFuture = scheduledFuture;
+        }
+
+    }
+
+    /**
+     * Wrapper for objects under management by the pool.
+     *
+     * GenericObjectPool and GenericKeyedObjectPool maintain references to all
+     * objects under management using maps keyed on the objects. This wrapper
+     * class ensures that objects can work as hash keys.
+     *
+     * @param <T> type of objects in the pool
+     */
+    static class IdentityWrapper<T> {
+        /** Wrapped object */
+        private final T instance;
+
+        /**
+         * Constructs a wrapper for an instance.
+         *
+         * @param instance object to wrap
+         */
+        public IdentityWrapper(final T instance) {
+            this.instance = instance;
+        }
+
+        @Override
+        @SuppressWarnings("rawtypes")
+        public boolean equals(final Object other) {
+            return other instanceof IdentityWrapper && ((IdentityWrapper) other).instance == instance;
+        }
+
+        /**
+         * @return the wrapped object
+         */
+        public T getObject() {
+            return instance;
+        }
+
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(instance);
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("IdentityWrapper [instance=");
+            builder.append(instance);
+            builder.append("]");
+            return builder.toString();
+        }
+    }
+
+    /**
+     * Maintains a cache of values for a single metric and reports
+     * statistics on the cached values.
+     */
+    private class StatsStore {
+
+        private static final int NULL = -1;
+        private final AtomicLong[] values;
+        private final int size;
+        private int index;
+
+        /**
+         * Constructs a StatsStore with the given cache size.
+         *
+         * @param size number of values to maintain in the cache.
+         */
+        StatsStore(final int size) {
+            this.size = size;
+            values = new AtomicLong[size];
+            for (int i = 0; i < size; i++) {
+                values[i] = new AtomicLong(NULL);
+            }
+        }
+
+        void add(final Duration value) {
+            add(value.toMillis());
+        }
+
+        /**
+         * Adds a value to the cache.  If the cache is full, one of the
+         * existing values is replaced by the new value.
+         *
+         * @param value new value to add to the cache.
+         */
+        synchronized void add(final long value) {
+            values[index].set(value);
+            index++;
+            if (index == size) {
+                index = 0;
+            }
+        }
+
+        /**
+         * Gets the current values as a List.
+         *
+         * @return the current values as a List.
+         */
+        synchronized List<AtomicLong> getCurrentValues() {
+            return Arrays.stream(values, 0, index).collect(Collectors.toList());
+        }
+
+        /**
+         * Gets the mean of the cached values.
+         *
+         * @return the mean of the cache, truncated to long
+         */
+        public long getMean() {
+            double result = 0;
+            int counter = 0;
+            for (int i = 0; i < size; i++) {
+                final long value = values[i].get();
+                if (value != NULL) {
+                    counter++;
+                    result = result * ((counter - 1) / (double) counter) + value / (double) counter;
+                }
+            }
+            return (long) result;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("StatsStore [");
+            // Only append what's been filled in.
+            builder.append(getCurrentValues());
+            builder.append("], size=");
+            builder.append(size);
+            builder.append(", index=");
+            builder.append(index);
+            builder.append("]");
+            return builder.toString();
+        }
+
+    }
+
     // Constants
     /**
      * The size of the caches used to store historical data for some attributes
      * so that rolling means may be calculated.
      */
     public static final int MEAN_TIMING_STATS_CACHE_SIZE = 100;
-
     private static final String EVICTION_POLICY_TYPE_NAME = EvictionPolicy.class.getName();
-
+    private static final Duration DEFAULT_REMOVE_ABANDONED_TIMEOUT = Duration.ofSeconds(Integer.MAX_VALUE);
     // Configuration attributes
-    private volatile int maxTotal =
-            GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
-    private volatile boolean blockWhenExhausted =
-            BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
-    private volatile long maxWaitMillis =
-            BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;
+    private volatile int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
+    private volatile boolean blockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
+    private volatile Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
     private volatile boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO;
     private final boolean fairness;
-    private volatile boolean testOnCreate =
-            BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
-    private volatile boolean testOnBorrow =
-            BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
-    private volatile boolean testOnReturn =
-            BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
-    private volatile boolean testWhileIdle =
-            BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
-    private volatile long timeBetweenEvictionRunsMillis =
-            BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
-    private volatile int numTestsPerEvictionRun =
-            BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
-    private volatile long minEvictableIdleTimeMillis =
-            BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
-    private volatile long softMinEvictableIdleTimeMillis =
-            BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private volatile boolean testOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
+    private volatile boolean testOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
+    private volatile boolean testOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
+    private volatile boolean testWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
+    private volatile Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
+    private volatile int numTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
+
+    private volatile Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
+    private volatile Duration softMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
     private volatile EvictionPolicy<T> evictionPolicy;
-    private volatile long evictorShutdownTimeoutMillis =
-            BaseObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
-
-
+    private volatile Duration evictorShutdownTimeoutDuration = BaseObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT;
     // Internal (primarily state) attributes
     final Object closeLock = new Object();
-    volatile boolean closed = false;
+    volatile boolean closed;
+
     final Object evictionLock = new Object();
-    private Evictor evictor = null; // @GuardedBy("evictionLock")
-    EvictionIterator evictionIterator = null; // @GuardedBy("evictionLock")
-    /*
+    private Evictor evictor; // @GuardedBy("evictionLock")
+    EvictionIterator evictionIterator; // @GuardedBy("evictionLock")
+
+    /**
      * 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.
      */
     private final WeakReference<ClassLoader> factoryClassLoader;
-
-
     // Monitoring (primarily JMX) attributes
     private final ObjectName objectName;
     private final String creationStackTrace;
-    private final AtomicLong borrowedCount = new AtomicLong(0);
-    private final AtomicLong returnedCount = new AtomicLong(0);
-    final AtomicLong createdCount = new AtomicLong(0);
-    final AtomicLong destroyedCount = new AtomicLong(0);
-    final AtomicLong destroyedByEvictorCount = new AtomicLong(0);
-    final AtomicLong destroyedByBorrowValidationCount = new AtomicLong(0);
+    private final AtomicLong borrowedCount = new AtomicLong();
+    private final AtomicLong returnedCount = new AtomicLong();
+    final AtomicLong createdCount = new AtomicLong();
+    final AtomicLong destroyedCount = new AtomicLong();
+    final AtomicLong destroyedByEvictorCount = new AtomicLong();
+    final AtomicLong destroyedByBorrowValidationCount = new AtomicLong();
+
     private final StatsStore activeTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE);
     private final StatsStore idleTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE);
     private final StatsStore waitTimes = new StatsStore(MEAN_TIMING_STATS_CACHE_SIZE);
-    private final AtomicLong maxBorrowWaitTimeMillis = new AtomicLong(0L);
-    private volatile SwallowedExceptionListener swallowedExceptionListener = null;
 
+    private final AtomicReference<Duration> maxBorrowWaitDuration = new AtomicReference<>(Duration.ZERO);
+
+    private volatile SwallowedExceptionListener swallowedExceptionListener;
+    private volatile boolean messageStatistics;
+
+    /** Additional configuration properties for abandoned object tracking. */
+    protected volatile AbandonedConfig abandonedConfig;
 
     /**
      * Handles JMX registration (if required) and the initialization required for
@@ -157,39 +417,78 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
         fairness = config.getFairness();
     }
 
-
     /**
-     * Returns the maximum number of objects that can be allocated by the pool
-     * (checked out to clients, or idle awaiting checkout) at a given time. When
-     * negative, there is no limit to the number of objects that can be
-     * managed by the pool at one time.
+     * Appends statistics if enabled.
+     * <p>
+     * Statistics may not accurately reflect snapshot state at the time of the exception because we do not want to lock the pool when gathering this
+     * information.
+     * </p>
      *
-     * @return the cap on the total number of object instances managed by the
-     *         pool.
-     *
-     * @see #setMaxTotal
+     * @param string The root string.
+     * @return The root string plus statistics.
      */
-    public final int getMaxTotal() {
-        return maxTotal;
+    String appendStats(final String string) {
+        return messageStatistics ? string + ", " + getStatsString() : string;
     }
 
     /**
-     * Sets the cap on the number of objects that can be allocated by the pool
-     * (checked out to clients, or idle awaiting checkout) at a given time. Use
-     * a negative value for no limit.
-     *
-     * @param maxTotal  The cap on the total number of object instances managed
-     *                  by the pool. Negative values mean that there is no limit
-     *                  to the number of objects allocated by the pool.
-     *
-     * @see #getMaxTotal
+     * Verifies that the pool is open.
+     * @throws IllegalStateException if the pool is closed.
      */
-    public final void setMaxTotal(final int maxTotal) {
-        this.maxTotal = maxTotal;
+    final void assertOpen() throws IllegalStateException {
+        if (isClosed()) {
+            throw new IllegalStateException("Pool not open");
+        }
+    }
+
+    /**
+     * Closes the pool, destroys the remaining idle objects and, if registered
+     * in JMX, deregisters it.
+     */
+    public abstract void close();
+
+    /**
+     * Creates a list of pooled objects to remove based on their state.
+     * @param abandonedConfig The abandoned configuration.
+     * @param allObjects PooledObject instances to consider.
+     * @return a list of pooled objects to remove based on their state.
+     */
+    ArrayList<PooledObject<T>> createRemoveList(final AbandonedConfig abandonedConfig, final Map<IdentityWrapper<T>, PooledObject<T>> allObjects) {
+        final Instant timeout = Instant.now().minus(abandonedConfig.getRemoveAbandonedTimeoutDuration());
+        final ArrayList<PooledObject<T>> remove = new ArrayList<>();
+        allObjects.values().forEach(pooledObject -> {
+            synchronized (pooledObject) {
+                if (pooledObject.getState() == PooledObjectState.ALLOCATED &&
+                        pooledObject.getLastUsedInstant().compareTo(timeout) <= 0) {
+                    pooledObject.markAbandoned();
+                    remove.add(pooledObject);
+                }
+            }
+        });
+        return remove;
     }
 
     /**
-     * Returns whether to block when the {@code borrowObject()} method is
+     * Tries to ensure that the configured minimum number of idle instances are
+     * available in the pool.
+     * @throws Exception if an error occurs creating idle instances
+     */
+    abstract void ensureMinIdle() throws Exception;
+
+    /**
+     * Perform {@code numTests} idle object eviction tests, evicting
+     * examined objects that meet the criteria for eviction. If
+     * {@code testWhileIdle} is true, examined objects are validated
+     * when visited (and removed if invalid); otherwise only objects that
+     * have been idle for more than {@code minEvicableIdleTimeMillis}
+     * are removed.
+     *
+     * @throws Exception when there is a problem evicting idle objects.
+     */
+    public abstract void evict() throws Exception;
+
+    /**
+     * Gets whether to block when the {@code borrowObject()} method is
      * invoked when the pool is exhausted (the maximum number of "active"
      * objects has been reached).
      *
@@ -203,101 +502,144 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
     }
 
     /**
-     * Sets whether to block when the {@code borrowObject()} method is
-     * invoked when the pool is exhausted (the maximum number of "active"
-     * objects has been reached).
+     * The total number of objects successfully borrowed from this pool over the
+     * lifetime of the pool.
+     * @return the borrowed object count
+     */
+    public final long getBorrowedCount() {
+        return borrowedCount.get();
+    }
+
+    /**
+     * The total number of objects created for this pool over the lifetime of
+     * the pool.
+     * @return the created object count
+     */
+    public final long getCreatedCount() {
+        return createdCount.get();
+    }
+
+    /**
+     * Provides the stack trace for the call that created this pool. JMX
+     * registration may trigger a memory leak so it is important that pools are
+     * deregistered when no longer used by calling the {@link #close()} method.
+     * This method is provided to assist with identifying code that creates but
+     * does not close it thereby creating a memory leak.
+     * @return pool creation stack trace
+     */
+    public final String getCreationStackTrace() {
+        return creationStackTrace;
+    }
+
+    /**
+     * The total number of objects destroyed by this pool as a result of failing
+     * validation during {@code borrowObject()} over the lifetime of the
+     * pool.
+     * @return validation destroyed object count
+     */
+    public final long getDestroyedByBorrowValidationCount() {
+        return destroyedByBorrowValidationCount.get();
+    }
+
+    /**
+     * The total number of objects destroyed by the evictor associated with this
+     * pool over the lifetime of the pool.
+     * @return the evictor destroyed object count
+     */
+    public final long getDestroyedByEvictorCount() {
+        return destroyedByEvictorCount.get();
+    }
+
+    /**
+     * The total number of objects destroyed by this pool over the lifetime of
+     * the pool.
+     * @return the destroyed object count
+     */
+    public final long getDestroyedCount() {
+        return destroyedCount.get();
+    }
+
+    /**
+     * Gets the duration to sleep between runs of the idle
+     * object evictor thread. When non-positive, no idle object evictor thread
+     * will be run.
      *
-     * @param blockWhenExhausted    {@code true} if
-     *                              {@code borrowObject()} should block
-     *                              when the pool is exhausted
+     * @return number of milliseconds to sleep between evictor runs
      *
-     * @see #getBlockWhenExhausted
+     * @see #setTimeBetweenEvictionRuns
+     * @since 2.11.0
      */
-    public final void setBlockWhenExhausted(final boolean blockWhenExhausted) {
-        this.blockWhenExhausted = blockWhenExhausted;
+    public final Duration getDurationBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
     }
 
     /**
-     * Initializes the receiver with the given configuration.
+     * Gets the {@link EvictionPolicy} defined for this pool.
      *
-     * @param config Initialization source.
+     * @return the eviction policy
+     * @since 2.4
+     * @since 2.6.0 Changed access from protected to public.
      */
-    protected void setConfig(final BaseObjectPoolConfig<T> config) {
-        setLifo(config.getLifo());
-        setMaxWaitMillis(config.getMaxWaitMillis());
-        setBlockWhenExhausted(config.getBlockWhenExhausted());
-        setTestOnCreate(config.getTestOnCreate());
-        setTestOnBorrow(config.getTestOnBorrow());
-        setTestOnReturn(config.getTestOnReturn());
-        setTestWhileIdle(config.getTestWhileIdle());
-        setNumTestsPerEvictionRun(config.getNumTestsPerEvictionRun());
-        setMinEvictableIdleTimeMillis(config.getMinEvictableIdleTimeMillis());
-        setTimeBetweenEvictionRunsMillis(config.getTimeBetweenEvictionRunsMillis());
-        setSoftMinEvictableIdleTimeMillis(config.getSoftMinEvictableIdleTimeMillis());
-        final EvictionPolicy<T> policy = config.getEvictionPolicy();
-        if (policy == null) {
-            // Use the class name (pre-2.6.0 compatible)
-            setEvictionPolicyClassName(config.getEvictionPolicyClassName());
-        } else {
-            // Otherwise, use the class (2.6.0 feature)
-            setEvictionPolicy(policy);
-        }
-        setEvictorShutdownTimeoutMillis(config.getEvictorShutdownTimeoutMillis());
+    public EvictionPolicy<T> getEvictionPolicy() {
+        return evictionPolicy;
     }
 
     /**
-     * Returns the maximum amount of time (in milliseconds) the
-     * {@code borrowObject()} method should block before throwing an
-     * exception when the pool is exhausted and
-     * {@link #getBlockWhenExhausted} is true. When less than 0, the
-     * {@code borrowObject()} method may block indefinitely.
+     * Gets the name of the {@link EvictionPolicy} implementation that is
+     * used by this pool.
      *
-     * @return the maximum number of milliseconds {@code borrowObject()}
-     *         will block.
+     * @return  The fully qualified class name of the {@link EvictionPolicy}
      *
-     * @see #setMaxWaitMillis
-     * @see #setBlockWhenExhausted
+     * @see #setEvictionPolicyClassName(String)
      */
-    public final long getMaxWaitMillis() {
-        return maxWaitMillis;
+    public final String getEvictionPolicyClassName() {
+        return evictionPolicy.getClass().getName();
     }
 
     /**
-     * Sets the maximum amount of time (in milliseconds) the
-     * {@code borrowObject()} method should block before throwing an
-     * exception when the pool is exhausted and
-     * {@link #getBlockWhenExhausted} is true. When less than 0, the
-     * {@code borrowObject()} method may block indefinitely.
-     *
-     * @param maxWaitMillis the maximum number of milliseconds
-     *                      {@code borrowObject()} will block or negative
-     *                      for indefinitely.
+     * Gets the timeout that will be used when waiting for the Evictor to
+     * shutdown if this pool is closed and it is the only pool still using the
+     * the value for the Evictor.
      *
-     * @see #getMaxWaitMillis
-     * @see #setBlockWhenExhausted
+     * @return  The timeout that will be used while waiting for
+     *          the Evictor to shut down.
+     * @since 2.10.0
+     * @deprecated Use {@link #getEvictorShutdownTimeoutDuration()}.
      */
-    public final void setMaxWaitMillis(final long maxWaitMillis) {
-        this.maxWaitMillis = maxWaitMillis;
+    @Deprecated
+    public final Duration getEvictorShutdownTimeout() {
+        return evictorShutdownTimeoutDuration;
     }
 
     /**
-     * Returns whether the pool has LIFO (last in, first out) behavior with
-     * respect to idle objects - always returning the most recently used object
-     * from the pool, or as a FIFO (first in, first out) queue, where the pool
-     * always returns the oldest object in the idle object pool.
+     * Gets the timeout that will be used when waiting for the Evictor to
+     * shutdown if this pool is closed and it is the only pool still using the
+     * the value for the Evictor.
      *
-     * @return {@code true} if the pool is configured with LIFO behavior
-     *         or {@code false} if the pool is configured with FIFO
-     *         behavior
+     * @return  The timeout that will be used while waiting for
+     *          the Evictor to shut down.
+     * @since 2.11.0
+     */
+    public final Duration getEvictorShutdownTimeoutDuration() {
+        return evictorShutdownTimeoutDuration;
+    }
+
+    /**
+     * Gets the timeout that will be used when waiting for the Evictor to
+     * shutdown if this pool is closed and it is the only pool still using the
+     * the value for the Evictor.
      *
-     * @see #setLifo
+     * @return  The timeout in milliseconds that will be used while waiting for
+     *          the Evictor to shut down.
+     * @deprecated Use {@link #getEvictorShutdownTimeoutDuration()}.
      */
-    public final boolean getLifo() {
-        return lifo;
+    @Deprecated
+    public final long getEvictorShutdownTimeoutMillis() {
+        return evictorShutdownTimeoutDuration.toMillis();
     }
 
     /**
-     * Returns whether or not the pool serves threads waiting to borrow objects fairly.
+     * Gets whether or not the pool serves threads waiting to borrow objects fairly.
      * True means that waiting threads are served as if waiting in a FIFO queue.
      *
      * @return {@code true} if waiting threads are to be served
@@ -308,196 +650,211 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
     }
 
     /**
-     * Sets whether the pool has LIFO (last in, first out) behavior with
+     * Provides the name under which the pool has been registered with the
+     * platform MBean server or {@code null} if the pool has not been
+     * registered.
+     * @return the JMX name
+     */
+    public final ObjectName getJmxName() {
+        return objectName;
+    }
+
+    /**
+     * Gets whether the pool has LIFO (last in, first out) behavior with
      * respect to idle objects - always returning the most recently used object
      * from the pool, or as a FIFO (first in, first out) queue, where the pool
      * always returns the oldest object in the idle object pool.
      *
-     * @param lifo  {@code true} if the pool is to be configured with LIFO
-     *              behavior or {@code false} if the pool is to be
-     *              configured with FIFO behavior
+     * @return {@code true} if the pool is configured with LIFO behavior
+     *         or {@code false} if the pool is configured with FIFO
+     *         behavior
      *
-     * @see #getLifo()
+     * @see #setLifo
      */
-    public final void setLifo(final boolean lifo) {
-        this.lifo = lifo;
+    public final boolean getLifo() {
+        return lifo;
     }
 
     /**
-     * Returns whether objects created for the pool will be validated before
-     * being returned from the {@code borrowObject()} method. Validation is
-     * performed by the {@code validateObject()} method of the factory
-     * associated with the pool. If the object fails to validate, then
-     * {@code borrowObject()} will fail.
+     * Gets whether this pool identifies and logs any abandoned objects.
      *
-     * @return {@code true} if newly created objects are validated before
-     *         being returned from the {@code borrowObject()} method
-     *
-     * @see #setTestOnCreate
+     * @return {@code true} if abandoned object removal is configured for this
+     *         pool and removal events are to be logged otherwise {@code false}
      *
-     * @since 2.2
+     * @see AbandonedConfig#getLogAbandoned()
+     * @since 2.11.0
      */
-    public final boolean getTestOnCreate() {
-        return testOnCreate;
+    public boolean getLogAbandoned() {
+        final AbandonedConfig ac = this.abandonedConfig;
+        return ac != null && ac.getLogAbandoned();
     }
 
     /**
-     * Sets whether objects created for the pool will be validated before
-     * being returned from the {@code borrowObject()} method. Validation is
-     * performed by the {@code validateObject()} method of the factory
-     * associated with the pool. If the object fails to validate, then
-     * {@code borrowObject()} will fail.
-     *
-     * @param testOnCreate  {@code true} if newly created objects should be
-     *                      validated before being returned from the
-     *                      {@code borrowObject()} method
-     *
-     * @see #getTestOnCreate
-     *
-     * @since 2.2
+     * Gets the maximum time a thread has waited to borrow objects from the pool.
+     * @return maximum wait time in milliseconds since the pool was created
      */
-    public final void setTestOnCreate(final boolean testOnCreate) {
-        this.testOnCreate = testOnCreate;
+    public final long getMaxBorrowWaitTimeMillis() {
+        return maxBorrowWaitDuration.get().toMillis();
     }
 
     /**
-     * Returns whether objects borrowed from the pool will be validated before
-     * being returned from the {@code borrowObject()} method. Validation is
-     * performed by the {@code validateObject()} method of the factory
-     * associated with the pool. If the object fails to validate, it will be
-     * removed from the pool and destroyed, and a new attempt will be made to
-     * borrow an object from the pool.
+     * Gets the maximum number of objects that can be allocated by the pool
+     * (checked out to clients, or idle awaiting checkout) at a given time. When
+     * negative, there is no limit to the number of objects that can be
+     * managed by the pool at one time.
      *
-     * @return {@code true} if objects are validated before being returned
-     *         from the {@code borrowObject()} method
+     * @return the cap on the total number of object instances managed by the
+     *         pool.
      *
-     * @see #setTestOnBorrow
+     * @see #setMaxTotal
      */
-    public final boolean getTestOnBorrow() {
-        return testOnBorrow;
+    public final int getMaxTotal() {
+        return maxTotal;
     }
 
     /**
-     * Sets whether objects borrowed from the pool will be validated before
-     * being returned from the {@code borrowObject()} method. Validation is
-     * performed by the {@code validateObject()} method of the factory
-     * associated with the pool. If the object fails to validate, it will be
-     * removed from the pool and destroyed, and a new attempt will be made to
-     * borrow an object from the pool.
+     * Gets the maximum duration the
+     * {@code borrowObject()} method should block before throwing an
+     * exception when the pool is exhausted and
+     * {@link #getBlockWhenExhausted} is true. When less than 0, the
+     * {@code borrowObject()} method may block indefinitely.
      *
-     * @param testOnBorrow  {@code true} if objects should be validated
-     *                      before being returned from the
-     *                      {@code borrowObject()} method
+     * @return the maximum number of milliseconds {@code borrowObject()}
+     *         will block.
      *
-     * @see #getTestOnBorrow
+     * @see #setMaxWait
+     * @see #setBlockWhenExhausted
+     * @since 2.11.0
      */
-    public final void setTestOnBorrow(final boolean testOnBorrow) {
-        this.testOnBorrow = testOnBorrow;
+    public final Duration getMaxWaitDuration() {
+        return maxWaitDuration;
     }
 
     /**
-     * Returns whether objects borrowed from the pool will be validated when
-     * they are returned to the pool via the {@code returnObject()} method.
-     * Validation is performed by the {@code validateObject()} method of
-     * the factory associated with the pool. Returning objects that fail validation
-     * are destroyed rather then being returned the pool.
+     * Gets the maximum amount of time (in milliseconds) the
+     * {@code borrowObject()} method should block before throwing an
+     * exception when the pool is exhausted and
+     * {@link #getBlockWhenExhausted} is true. When less than 0, the
+     * {@code borrowObject()} method may block indefinitely.
      *
-     * @return {@code true} if objects are validated on return to
-     *         the pool via the {@code returnObject()} method
+     * @return the maximum number of milliseconds {@code borrowObject()}
+     *         will block.
      *
-     * @see #setTestOnReturn
+     * @see #setMaxWait
+     * @see #setBlockWhenExhausted
+     * @deprecated Use {@link #getMaxWaitDuration()}.
      */
-    public final boolean getTestOnReturn() {
-        return testOnReturn;
+    @Deprecated
+    public final long getMaxWaitMillis() {
+        return maxWaitDuration.toMillis();
     }
 
     /**
-     * Sets whether objects borrowed from the pool will be validated when
-     * they are returned to the pool via the {@code returnObject()} method.
-     * Validation is performed by the {@code validateObject()} method of
-     * the factory associated with the pool. Returning objects that fail validation
-     * are destroyed rather then being returned the pool.
-     *
-     * @param testOnReturn {@code true} if objects are validated on
-     *                     return to the pool via the
-     *                     {@code returnObject()} method
-     *
-     * @see #getTestOnReturn
+     * The mean time objects are active for based on the last {@link
+     * #MEAN_TIMING_STATS_CACHE_SIZE} objects returned to the pool.
+     * @return mean time an object has been checked out from the pool among
+     * recently returned objects
      */
-    public final void setTestOnReturn(final boolean testOnReturn) {
-        this.testOnReturn = testOnReturn;
+    public final long getMeanActiveTimeMillis() {
+        return activeTimes.getMean();
     }
 
     /**
-     * Returns whether objects sitting idle in the pool will be validated by the
-     * idle object evictor (if any - see
-     * {@link #setTimeBetweenEvictionRunsMillis(long)}). Validation is performed
-     * by the {@code validateObject()} method of the factory associated
-     * with the pool. If the object fails to validate, it will be removed from
-     * the pool and destroyed.
-     *
-     * @return {@code true} if objects will be validated by the evictor
+     * The mean time threads wait to borrow an object based on the last {@link
+     * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool.
+     * @return mean time in milliseconds that a recently served thread has had
+     * to wait to borrow an object from the pool
+     */
+    public final long getMeanBorrowWaitTimeMillis() {
+        return waitTimes.getMean();
+    }
+
+    /**
+     * The mean time objects are idle for based on the last {@link
+     * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool.
+     * @return mean time an object has been idle in the pool among recently
+     * borrowed objects
+     */
+    public final long getMeanIdleTimeMillis() {
+        return idleTimes.getMean();
+    }
+
+    /**
+     * Gets whether to include statistics in exception messages.
+     * <p>
+     * Statistics may not accurately reflect snapshot state at the time of the exception because we do not want to lock the pool when gathering this
+     * information.
+     * </p>
      *
-     * @see #setTestWhileIdle
-     * @see #setTimeBetweenEvictionRunsMillis
+     * @return whether to include statistics in exception messages.
+     * @since 2.11.0
      */
-    public final boolean getTestWhileIdle() {
-        return testWhileIdle;
+    public boolean getMessageStatistics() {
+        return messageStatistics;
     }
 
     /**
-     * Returns whether objects sitting idle in the pool will be validated by the
-     * idle object evictor (if any - see
-     * {@link #setTimeBetweenEvictionRunsMillis(long)}). Validation is performed
-     * by the {@code validateObject()} method of the factory associated
-     * with the pool. If the object fails to validate, it will be removed from
-     * the pool and destroyed.  Note that setting this property has no effect
-     * unless the idle object evictor is enabled by setting
-     * {@code timeBetweenEvictionRunsMillis} to a positive value.
+     * Gets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
      *
-     * @param testWhileIdle
-     *            {@code true} so objects will be validated by the evictor
+     * @return minimum amount of time an object may sit idle in the pool before
+     *         it is eligible for eviction
      *
-     * @see #getTestWhileIdle
+     * @see #setMinEvictableIdleTimeMillis
      * @see #setTimeBetweenEvictionRunsMillis
+     * @since 2.11.0
      */
-    public final void setTestWhileIdle(final boolean testWhileIdle) {
-        this.testWhileIdle = testWhileIdle;
+    public final Duration getMinEvictableIdleDuration() {
+        return minEvictableIdleDuration;
     }
 
     /**
-     * Returns the number of milliseconds to sleep between runs of the idle
-     * object evictor thread. When non-positive, no idle object evictor thread
-     * will be run.
+     * Gets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
      *
-     * @return number of milliseconds to sleep between evictor runs
+     * @return minimum amount of time an object may sit idle in the pool before
+     *         it is eligible for eviction
      *
+     * @see #setMinEvictableIdleTimeMillis
      * @see #setTimeBetweenEvictionRunsMillis
+     * @since 2.10.0
+     * @deprecated Use {@link #getMinEvictableIdleDuration()}.
      */
-    public final long getTimeBetweenEvictionRunsMillis() {
-        return timeBetweenEvictionRunsMillis;
+    @Deprecated
+    public final Duration getMinEvictableIdleTime() {
+        return minEvictableIdleDuration;
     }
 
     /**
-     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread.
-     * <ul>
-     * <li>When positive, the idle object evictor thread starts.</li>
-     * <li>When non-positive, no idle object evictor thread runs.</li>
-     * </ul>
+     * Gets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
      *
-     * @param timeBetweenEvictionRunsMillis
-     *            number of milliseconds to sleep between evictor runs
+     * @return minimum amount of time an object may sit idle in the pool before
+     *         it is eligible for eviction
      *
-     * @see #getTimeBetweenEvictionRunsMillis
+     * @see #setMinEvictableIdleTimeMillis
+     * @see #setTimeBetweenEvictionRunsMillis
+     * @deprecated Use {@link #getMinEvictableIdleDuration()}.
      */
-    public final void setTimeBetweenEvictionRunsMillis(
-            final long timeBetweenEvictionRunsMillis) {
-        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
-        startEvictor(timeBetweenEvictionRunsMillis);
+    @Deprecated
+    public final long getMinEvictableIdleTimeMillis() {
+        return minEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Returns the maximum number of objects to examine during each run (if any)
+     * The number of instances currently idle in this pool.
+     * @return count of instances available for checkout from the pool
+     */
+    public abstract int getNumIdle();
+
+    /**
+     * Gets the maximum number of objects to examine during each run (if any)
      * of the idle object evictor thread. When positive, the number of tests
      * performed for a run will be the minimum of the configured value and the
      * number of idle instances in the pool. When negative, the number of tests
@@ -516,80 +873,120 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
     }
 
     /**
-     * Sets the maximum number of objects to examine during each run (if any)
-     * of the idle object evictor thread. When positive, the number of tests
-     * performed for a run will be the minimum of the configured value and the
-     * number of idle instances in the pool. When negative, the number of tests
-     * performed will be <code>ceil({@link #getNumIdle}/
-     * abs({@link #getNumTestsPerEvictionRun}))</code> which means that when the
-     * value is {@code -n} roughly one nth of the idle objects will be
-     * tested per run.
+     * Gets whether a check is made for abandoned objects when an object is borrowed
+     * from this pool.
      *
-     * @param numTestsPerEvictionRun
-     *            max number of objects to examine during each evictor run
+     * @return {@code true} if abandoned object removal is configured to be
+     *         activated by borrowObject otherwise {@code false}
      *
-     * @see #getNumTestsPerEvictionRun
-     * @see #setTimeBetweenEvictionRunsMillis
+     * @see AbandonedConfig#getRemoveAbandonedOnBorrow()
+     * @since 2.11.0
      */
-    public final void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
-        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
+    public boolean getRemoveAbandonedOnBorrow() {
+        final AbandonedConfig ac = this.abandonedConfig;
+        return ac != null && ac.getRemoveAbandonedOnBorrow();
     }
 
     /**
-     * Returns the minimum amount of time an object may sit idle in the pool
-     * before it is eligible for eviction by the idle object evictor (if any -
-     * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive,
-     * no objects will be evicted from the pool due to idle time alone.
+     * Gets whether a check is made for abandoned objects when the evictor runs.
      *
-     * @return minimum amount of time an object may sit idle in the pool before
-     *         it is eligible for eviction
+     * @return {@code true} if abandoned object removal is configured to be
+     *         activated when the evictor runs otherwise {@code false}
      *
-     * @see #setMinEvictableIdleTimeMillis
-     * @see #setTimeBetweenEvictionRunsMillis
+     * @see AbandonedConfig#getRemoveAbandonedOnMaintenance()
+     * @since 2.11.0
      */
-    public final long getMinEvictableIdleTimeMillis() {
-        return minEvictableIdleTimeMillis;
+    public boolean getRemoveAbandonedOnMaintenance() {
+        final AbandonedConfig ac = this.abandonedConfig;
+        return ac != null && ac.getRemoveAbandonedOnMaintenance();
     }
 
     /**
-     * Sets the minimum amount of time an object may sit idle in the pool
+     * Gets the timeout before which an object will be considered to be
+     * abandoned by this pool.
+     *
+     * @return The abandoned object timeout in seconds if abandoned object
+     *         removal is configured for this pool; Integer.MAX_VALUE otherwise.
+     *
+     * @see AbandonedConfig#getRemoveAbandonedTimeoutDuration()
+     * @see AbandonedConfig#getRemoveAbandonedTimeoutDuration()
+     * @deprecated Use {@link #getRemoveAbandonedTimeoutDuration()}.
+     * @since 2.11.0
+     */
+    @Deprecated
+    public int getRemoveAbandonedTimeout() {
+        return (int) getRemoveAbandonedTimeoutDuration().getSeconds();
+    }
+
+    /**
+     * Gets the timeout before which an object will be considered to be
+     * abandoned by this pool.
+     *
+     * @return The abandoned object timeout in seconds if abandoned object
+     *         removal is configured for this pool; Integer.MAX_VALUE otherwise.
+     *
+     * @see AbandonedConfig#getRemoveAbandonedTimeoutDuration()
+     * @since 2.11.0
+     */
+    public Duration getRemoveAbandonedTimeoutDuration() {
+        final AbandonedConfig ac = this.abandonedConfig;
+        return ac != null ? ac.getRemoveAbandonedTimeoutDuration() : DEFAULT_REMOVE_ABANDONED_TIMEOUT;
+    }
+
+    /**
+     * The total number of objects returned to this pool over the lifetime of
+     * the pool. This excludes attempts to return the same object multiple
+     * times.
+     * @return the returned object count
+     */
+    public final long getReturnedCount() {
+        return returnedCount.get();
+    }
+
+    /**
+     * Gets the minimum amount of time an object may sit idle in the pool
      * before it is eligible for eviction by the idle object evictor (if any -
-     * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive,
-     * no objects will be evicted from the pool due to idle time alone.
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}),
+     * with the extra condition that at least {@code minIdle} object
+     * instances remain in the pool. This setting is overridden by
+     * {@link #getMinEvictableIdleTime} (that is, if
+     * {@link #getMinEvictableIdleTime} is positive, then
+     * {@link #getSoftMinEvictableIdleTime} is ignored).
      *
-     * @param minEvictableIdleTimeMillis
-     *            minimum amount of time an object may sit idle in the pool
-     *            before it is eligible for eviction
+     * @return minimum amount of time an object may sit idle in the pool before
+     *         it is eligible for eviction if minIdle instances are available
      *
-     * @see #getMinEvictableIdleTimeMillis
-     * @see #setTimeBetweenEvictionRunsMillis
+     * @see #setSoftMinEvictableIdle(Duration)
+     * @since 2.11.0
      */
-    public final void setMinEvictableIdleTimeMillis(
-            final long minEvictableIdleTimeMillis) {
-        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+    public final Duration getSoftMinEvictableIdleDuration() {
+        return softMinEvictableIdleDuration;
     }
 
     /**
-     * Returns the minimum amount of time an object may sit idle in the pool
+     * Gets the minimum amount of time an object may sit idle in the pool
      * before it is eligible for eviction by the idle object evictor (if any -
-     * see {@link #setTimeBetweenEvictionRunsMillis(long)}),
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}),
      * with the extra condition that at least {@code minIdle} object
      * instances remain in the pool. This setting is overridden by
-     * {@link #getMinEvictableIdleTimeMillis} (that is, if
-     * {@link #getMinEvictableIdleTimeMillis} is positive, then
-     * {@link #getSoftMinEvictableIdleTimeMillis} is ignored).
+     * {@link #getMinEvictableIdleTime} (that is, if
+     * {@link #getMinEvictableIdleTime} is positive, then
+     * {@link #getSoftMinEvictableIdleTime} is ignored).
      *
      * @return minimum amount of time an object may sit idle in the pool before
      *         it is eligible for eviction if minIdle instances are available
      *
-     * @see #setSoftMinEvictableIdleTimeMillis
+     * @see #setSoftMinEvictableIdle(Duration)
+     * @since 2.10.0
+     * @deprecated Use {@link #getSoftMinEvictableIdleDuration}.
      */
-    public final long getSoftMinEvictableIdleTimeMillis() {
-        return softMinEvictableIdleTimeMillis;
+    @Deprecated
+    public final Duration getSoftMinEvictableIdleTime() {
+        return softMinEvictableIdleDuration;
     }
 
     /**
-     * Sets the minimum amount of time an object may sit idle in the pool
+     * Gets the minimum amount of time an object may sit idle in the pool
      * before it is eligible for eviction by the idle object evictor (if any -
      * see {@link #setTimeBetweenEvictionRunsMillis(long)}),
      * with the extra condition that at least {@code minIdle} object
@@ -598,773 +995,832 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
      * {@link #getMinEvictableIdleTimeMillis} is positive, then
      * {@link #getSoftMinEvictableIdleTimeMillis} is ignored).
      *
-     * @param softMinEvictableIdleTimeMillis
-     *            minimum amount of time an object may sit idle in the pool
-     *            before it is eligible for eviction if minIdle instances are
-     *            available
+     * @return minimum amount of time an object may sit idle in the pool before
+     *         it is eligible for eviction if minIdle instances are available
      *
-     * @see #getSoftMinEvictableIdleTimeMillis
+     * @see #setSoftMinEvictableIdleTimeMillis
+     * @deprecated Use {@link #getSoftMinEvictableIdleTime()}.
      */
-    public final void setSoftMinEvictableIdleTimeMillis(
-            final long softMinEvictableIdleTimeMillis) {
-        this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
+    @Deprecated
+    public final long getSoftMinEvictableIdleTimeMillis() {
+        return softMinEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Returns the name of the {@link EvictionPolicy} implementation that is
-     * used by this pool.
-     *
-     * @return  The fully qualified class name of the {@link EvictionPolicy}
+     * Gets the stack trace of an exception as a string.
+     * @param e exception to trace
+     * @return exception stack trace as a string
+     */
+    private String getStackTrace(final Exception e) {
+        // Need the exception in string form to prevent the retention of
+        // references to classes in the stack trace that could trigger a memory
+        // leak in a container environment.
+        final Writer w = new StringWriter();
+        final PrintWriter pw = new PrintWriter(w);
+        e.printStackTrace(pw);
+        return w.toString();
+    }
+
+    /**
+     * Gets a statistics string.
      *
-     * @see #setEvictionPolicyClassName(String)
+     * @return  a statistics string.
      */
-    public final String getEvictionPolicyClassName() {
-        return evictionPolicy.getClass().getName();
+    @SuppressWarnings("boxing") // Commons Pool uses auto-boxing
+    String getStatsString() {
+        // Simply listed in AB order.
+        return String.format(
+                "activeTimes=%s, blockWhenExhausted=%s, borrowedCount=%,d, closed=%s, createdCount=%,d, destroyedByBorrowValidationCount=%,d, " +
+                        "destroyedByEvictorCount=%,d, evictorShutdownTimeoutDuration=%s, fairness=%s, idleTimes=%s, lifo=%s, maxBorrowWaitDuration=%s, " +
+                        "maxTotal=%s, maxWaitDuration=%s, minEvictableIdleDuration=%s, numTestsPerEvictionRun=%s, returnedCount=%s, " +
+                        "softMinEvictableIdleDuration=%s, testOnBorrow=%s, testOnCreate=%s, testOnReturn=%s, testWhileIdle=%s, " +
+                        "durationBetweenEvictionRuns=%s, waitTimes=%s",
+                activeTimes.getCurrentValues(), blockWhenExhausted, borrowedCount.get(), closed, createdCount.get(), destroyedByBorrowValidationCount.get(),
+                destroyedByEvictorCount.get(), evictorShutdownTimeoutDuration, fairness, idleTimes.getCurrentValues(), lifo, maxBorrowWaitDuration.get(),
+                maxTotal, maxWaitDuration, minEvictableIdleDuration, numTestsPerEvictionRun, returnedCount, softMinEvictableIdleDuration, testOnBorrow,
+                testOnCreate, testOnReturn, testWhileIdle, durationBetweenEvictionRuns, waitTimes.getCurrentValues());
     }
 
     /**
-     * Sets the eviction policy for this pool.
+     * Gets the listener used (if any) to receive notifications of exceptions
+     * unavoidably swallowed by the pool.
      *
-     * @param evictionPolicy
-     *            the eviction policy for this pool.
-     * @since 2.6.0
+     * @return The listener or {@code null} for no listener
      */
-    public void setEvictionPolicy(final EvictionPolicy<T> evictionPolicy) {
-        this.evictionPolicy = evictionPolicy;
+    public final SwallowedExceptionListener getSwallowedExceptionListener() {
+        return swallowedExceptionListener;
     }
 
     /**
-     * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to
-     * load the class using the given class loader. If that fails, use the class loader for the {@link EvictionPolicy}
-     * interface.
+     * Gets whether objects borrowed from the pool will be validated before
+     * being returned from the {@code borrowObject()} method. Validation is
+     * performed by the {@code validateObject()} method of the factory
+     * associated with the pool. If the object fails to validate, it will be
+     * removed from the pool and destroyed, and a new attempt will be made to
+     * borrow an object from the pool.
      *
-     * @param evictionPolicyClassName
-     *            the fully qualified class name of the new eviction policy
-     * @param classLoader
-     *            the class loader to load the given {@code evictionPolicyClassName}.
+     * @return {@code true} if objects are validated before being returned
+     *         from the {@code borrowObject()} method
      *
-     * @see #getEvictionPolicyClassName()
-     * @since 2.6.0 If loading the class using the given class loader fails, use the class loader for the
-     *        {@link EvictionPolicy} interface.
+     * @see #setTestOnBorrow
      */
-    public final void setEvictionPolicyClassName(final String evictionPolicyClassName, final ClassLoader classLoader) {
-        // Getting epClass here and now best matches the caller's environment
-        final Class<?> epClass = EvictionPolicy.class;
-        final ClassLoader epClassLoader = epClass.getClassLoader();
-        try {
-            try {
-                setEvictionPolicy(evictionPolicyClassName, classLoader);
-            } catch (final ClassCastException | ClassNotFoundException e) {
-                setEvictionPolicy(evictionPolicyClassName, epClassLoader);
-            }
-        } catch (final ClassCastException e) {
-            throw new IllegalArgumentException("Class " + evictionPolicyClassName + " from class loaders [" +
-                    classLoader + ", " + epClassLoader + "] do not implement " + EVICTION_POLICY_TYPE_NAME);
-        } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException |
-                InvocationTargetException | NoSuchMethodException e) {
-            final String exMessage = "Unable to create " + EVICTION_POLICY_TYPE_NAME + " instance of type " +
-                    evictionPolicyClassName;
-            throw new IllegalArgumentException(exMessage, e);
-        }
+    public final boolean getTestOnBorrow() {
+        return testOnBorrow;
     }
 
     /**
-     * Sets the eviction policy.
+     * Gets whether objects created for the pool will be validated before
+     * being returned from the {@code borrowObject()} method. Validation is
+     * performed by the {@code validateObject()} method of the factory
+     * associated with the pool. If the object fails to validate, then
+     * {@code borrowObject()} will fail.
      *
-     * @param className Eviction policy class name.
-     * @param classLoader Load the class from this class loader.
+     * @return {@code true} if newly created objects are validated before
+     *         being returned from the {@code borrowObject()} method
+     *
+     * @see #setTestOnCreate
+     *
+     * @since 2.2
      */
-    @SuppressWarnings("unchecked")
-    private void setEvictionPolicy(final String className, final ClassLoader classLoader)
-            throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
-        final Class<?> clazz = Class.forName(className, true, classLoader);
-        final Object policy = clazz.getConstructor().newInstance();
-        this.evictionPolicy = (EvictionPolicy<T>) policy;
+    public final boolean getTestOnCreate() {
+        return testOnCreate;
     }
 
     /**
-     * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to
-     * load the class using the thread context class loader. If that fails, the use the class loader for the
-     * {@link EvictionPolicy} interface.
+     * Gets whether objects borrowed from the pool will be validated when
+     * they are returned to the pool via the {@code returnObject()} method.
+     * Validation is performed by the {@code validateObject()} method of
+     * the factory associated with the pool. Returning objects that fail validation
+     * are destroyed rather then being returned the pool.
      *
-     * @param evictionPolicyClassName
-     *            the fully qualified class name of the new eviction policy
+     * @return {@code true} if objects are validated on return to
+     *         the pool via the {@code returnObject()} method
      *
-     * @see #getEvictionPolicyClassName()
-     * @since 2.6.0 If loading the class using the thread context class loader fails, use the class loader for the
-     *        {@link EvictionPolicy} interface.
+     * @see #setTestOnReturn
      */
-    public final void setEvictionPolicyClassName(final String evictionPolicyClassName) {
-        setEvictionPolicyClassName(evictionPolicyClassName, Thread.currentThread().getContextClassLoader());
+    public final boolean getTestOnReturn() {
+        return testOnReturn;
     }
 
     /**
-     * Gets the timeout that will be used when waiting for the Evictor to
-     * shutdown if this pool is closed and it is the only pool still using the
-     * the value for the Evictor.
+     * Gets whether objects sitting idle in the pool will be validated by the
+     * idle object evictor (if any - see
+     * {@link #setTimeBetweenEvictionRuns(Duration)}). Validation is performed
+     * by the {@code validateObject()} method of the factory associated
+     * with the pool. If the object fails to validate, it will be removed from
+     * the pool and destroyed.
      *
-     * @return  The timeout in milliseconds that will be used while waiting for
-     *          the Evictor to shut down.
+     * @return {@code true} if objects will be validated by the evictor
+     *
+     * @see #setTestWhileIdle
+     * @see #setTimeBetweenEvictionRunsMillis
      */
-    public final long getEvictorShutdownTimeoutMillis() {
-        return evictorShutdownTimeoutMillis;
+    public final boolean getTestWhileIdle() {
+        return testWhileIdle;
     }
 
     /**
-     * Sets the timeout that will be used when waiting for the Evictor to
-     * shutdown if this pool is closed and it is the only pool still using the
-     * the value for the Evictor.
+     * Gets the duration to sleep between runs of the idle
+     * object evictor thread. When non-positive, no idle object evictor thread
+     * will be run.
      *
-     * @param evictorShutdownTimeoutMillis  the timeout in milliseconds that
-     *                                      will be used while waiting for the
-     *                                      Evictor to shut down.
+     * @return number of milliseconds to sleep between evictor runs
+     *
+     * @see #setTimeBetweenEvictionRuns
+     * @since 2.10.0
+     * @deprecated {@link #getDurationBetweenEvictionRuns()}.
      */
-    public final void setEvictorShutdownTimeoutMillis(
-            final long evictorShutdownTimeoutMillis) {
-        this.evictorShutdownTimeoutMillis = evictorShutdownTimeoutMillis;
+    @Deprecated
+    public final Duration getTimeBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
     }
 
     /**
-     * Closes the pool, destroys the remaining idle objects and, if registered
-     * in JMX, deregisters it.
-     */
-    public abstract void close();
-
-    /**
-     * Has this pool instance been closed.
-     * @return {@code true} when this pool has been closed.
+     * Gets the number of milliseconds to sleep between runs of the idle
+     * object evictor thread. When non-positive, no idle object evictor thread
+     * will be run.
+     *
+     * @return number of milliseconds to sleep between evictor runs
+     *
+     * @see #setTimeBetweenEvictionRunsMillis
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
      */
-    public final boolean isClosed() {
-        return closed;
+    @Deprecated
+    public final long getTimeBetweenEvictionRunsMillis() {
+        return durationBetweenEvictionRuns.toMillis();
     }
 
-    /**
-     * <p>Perform {@code numTests} idle object eviction tests, evicting
-     * examined objects that meet the criteria for eviction. If
-     * {@code testWhileIdle} is true, examined objects are validated
-     * when visited (and removed if invalid); otherwise only objects that
-     * have been idle for more than {@code minEvicableIdleTimeMillis}
-     * are removed.</p>
-     *
-     * @throws Exception when there is a problem evicting idle objects.
-     */
-    public abstract void evict() throws Exception;
+    // Monitoring (primarily JMX) related methods
 
     /**
-     * Returns the {@link EvictionPolicy} defined for this pool.
+     * Gets whether or not abandoned object removal is configured for this pool.
      *
-     * @return the eviction policy
-     * @since 2.4
-     * @since 2.6.0 Changed access from protected to public.
+     * @return true if this pool is configured to detect and remove
+     * abandoned objects
+     * @since 2.11.0
      */
-    public EvictionPolicy<T> getEvictionPolicy() {
-        return evictionPolicy;
+    public boolean isAbandonedConfig() {
+        return abandonedConfig != null;
     }
 
     /**
-     * Verifies that the pool is open.
-     * @throws IllegalStateException if the pool is closed.
+     * Has this pool instance been closed.
+     * @return {@code true} when this pool has been closed.
      */
-    final void assertOpen() throws IllegalStateException {
-        if (isClosed()) {
-            throw new IllegalStateException("Pool not open");
-        }
+    public final boolean isClosed() {
+        return closed;
     }
 
     /**
-     * <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>
+     * Registers the pool with the platform MBean server.
+     * The registered name will be
+     * {@code jmxNameBase + jmxNamePrefix + i} where i is the least
+     * integer greater than or equal to 1 such that the name is not already
+     * registered. Swallows MBeanRegistrationException, NotCompliantMBeanException
+     * returning null.
      *
-     * @param delay time in milliseconds before start and between eviction runs
+     * @param config Pool configuration
+     * @param jmxNameBase default base JMX name for this pool
+     * @param jmxNamePrefix name prefix
+     * @return registered ObjectName, null if registration fails
      */
-    final void startEvictor(final long delay) {
-        synchronized (evictionLock) {
-            if (evictor == null) { // Starting evictor for the first time or after a cancel
-                if (delay > 0) {   // Starting new evictor
-                    evictor = new Evictor();
-                    EvictionTimer.schedule(evictor, delay, delay);
+    private ObjectName jmxRegister(final BaseObjectPoolConfig<T> config,
+            final String jmxNameBase, String jmxNamePrefix) {
+        ObjectName newObjectName = null;
+        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+        int i = 1;
+        boolean registered = false;
+        String base = config.getJmxNameBase();
+        if (base == null) {
+            base = jmxNameBase;
+        }
+        while (!registered) {
+            try {
+                ObjectName objName;
+                // Skip the numeric suffix for the first pool in case there is
+                // only one so the names are cleaner.
+                if (i == 1) {
+                    objName = new ObjectName(base + jmxNamePrefix);
+                } else {
+                    objName = new ObjectName(base + jmxNamePrefix + i);
                 }
-            } else {  // Stop or restart of existing evictor
-                if (delay > 0) { // Restart
-                    synchronized (EvictionTimer.class) { // Ensure no cancel can happen between cancel / schedule calls
-                        EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS, true);
-                        evictor = null;
-                        evictionIterator = null;
-                        evictor = new Evictor();
-                        EvictionTimer.schedule(evictor, delay, delay);
-                    }
-                } else { // Stopping evictor
-                    EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS, false);
+                mbs.registerMBean(this, objName);
+                newObjectName = objName;
+                registered = true;
+            } catch (final MalformedObjectNameException e) {
+                if (BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX.equals(
+                        jmxNamePrefix) && jmxNameBase.equals(base)) {
+                    // Shouldn't happen. Skip registration if it does.
+                    registered = true;
+                } else {
+                    // Must be an invalid name. Use the defaults instead.
+                    jmxNamePrefix =
+                            BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX;
+                    base = jmxNameBase;
                 }
+            } catch (final InstanceAlreadyExistsException e) {
+                // Increment the index and try again
+                i++;
+            } catch (final MBeanRegistrationException | NotCompliantMBeanException e) {
+                // Shouldn't happen. Skip registration if it does.
+                registered = true;
             }
         }
+        return newObjectName;
     }
 
     /**
-     * Stops the evictor.
+     * Unregisters this pool's MBean.
      */
-    void stopEvictor() {
-        startEvictor(-1L);
+    final void jmxUnregister() {
+        if (objectName != null) {
+            try {
+                ManagementFactory.getPlatformMBeanServer().unregisterMBean(objectName);
+            } catch (final MBeanRegistrationException | InstanceNotFoundException e) {
+                swallowException(e);
+            }
+        }
     }
-    /**
-     * Tries to ensure that the configured minimum number of idle instances are
-     * available in the pool.
-     * @throws Exception if an error occurs creating idle instances
-     */
-    abstract void ensureMinIdle() throws Exception;
-
-
-    // Monitoring (primarily JMX) related methods
 
     /**
-     * Provides the name under which the pool has been registered with the
-     * platform MBean server or {@code null} if the pool has not been
-     * registered.
-     * @return the JMX name
+     * Marks the object as returning to the pool.
+     * @param pooledObject instance to return to the keyed pool
      */
-    public final ObjectName getJmxName() {
-        return objectName;
+    protected void markReturningState(final PooledObject<T> pooledObject) {
+        synchronized (pooledObject) {
+            if (pooledObject.getState() != PooledObjectState.ALLOCATED) {
+                throw new IllegalStateException("Object has already been returned to this pool or is invalid");
+            }
+            pooledObject.markReturning(); // Keep from being marked abandoned
+        }
     }
 
     /**
-     * Provides the stack trace for the call that created this pool. JMX
-     * registration may trigger a memory leak so it is important that pools are
-     * deregistered when no longer used by calling the {@link #close()} method.
-     * This method is provided to assist with identifying code that creates but
-     * does not close it thereby creating a memory leak.
-     * @return pool creation stack trace
+     * Sets the abandoned object removal configuration.
+     *
+     * @param abandonedConfig the new configuration to use. This is used by value.
+     *
+     * @see AbandonedConfig
+     * @since 2.11.0
      */
-    public final String getCreationStackTrace() {
-        return creationStackTrace;
+    public void setAbandonedConfig(final AbandonedConfig abandonedConfig) {
+        this.abandonedConfig = AbandonedConfig.copy(abandonedConfig);
     }
 
     /**
-     * The total number of objects successfully borrowed from this pool over the
-     * lifetime of the pool.
-     * @return the borrowed object count
+     * Sets whether to block when the {@code borrowObject()} method is
+     * invoked when the pool is exhausted (the maximum number of "active"
+     * objects has been reached).
+     *
+     * @param blockWhenExhausted    {@code true} if
+     *                              {@code borrowObject()} should block
+     *                              when the pool is exhausted
+     *
+     * @see #getBlockWhenExhausted
      */
-    public final long getBorrowedCount() {
-        return borrowedCount.get();
+    public final void setBlockWhenExhausted(final boolean blockWhenExhausted) {
+        this.blockWhenExhausted = blockWhenExhausted;
     }
 
     /**
-     * The total number of objects returned to this pool over the lifetime of
-     * the pool. This excludes attempts to return the same object multiple
-     * times.
-     * @return the returned object count
+     * Initializes the receiver with the given configuration.
+     *
+     * @param config Initialization source.
      */
-    public final long getReturnedCount() {
-        return returnedCount.get();
-    }
-
-    /**
-     * The total number of objects created for this pool over the lifetime of
-     * the pool.
-     * @return the created object count
-     */
-    public final long getCreatedCount() {
-        return createdCount.get();
+    protected void setConfig(final BaseObjectPoolConfig<T> config) {
+        setLifo(config.getLifo());
+        setMaxWait(config.getMaxWaitDuration());
+        setBlockWhenExhausted(config.getBlockWhenExhausted());
+        setTestOnCreate(config.getTestOnCreate());
+        setTestOnBorrow(config.getTestOnBorrow());
+        setTestOnReturn(config.getTestOnReturn());
+        setTestWhileIdle(config.getTestWhileIdle());
+        setNumTestsPerEvictionRun(config.getNumTestsPerEvictionRun());
+        setMinEvictableIdle(config.getMinEvictableIdleDuration());
+        setTimeBetweenEvictionRuns(config.getDurationBetweenEvictionRuns());
+        setSoftMinEvictableIdle(config.getSoftMinEvictableIdleDuration());
+        final EvictionPolicy<T> policy = config.getEvictionPolicy();
+        if (policy == null) {
+            // Use the class name (pre-2.6.0 compatible)
+            setEvictionPolicyClassName(config.getEvictionPolicyClassName());
+        } else {
+            // Otherwise, use the class (2.6.0 feature)
+            setEvictionPolicy(policy);
+        }
+        setEvictorShutdownTimeout(config.getEvictorShutdownTimeoutDuration());
     }
 
     /**
-     * The total number of objects destroyed by this pool over the lifetime of
-     * the pool.
-     * @return the destroyed object count
+     * Sets the eviction policy for this pool.
+     *
+     * @param evictionPolicy
+     *            the eviction policy for this pool.
+     * @since 2.6.0
      */
-    public final long getDestroyedCount() {
-        return destroyedCount.get();
+    public void setEvictionPolicy(final EvictionPolicy<T> evictionPolicy) {
+        this.evictionPolicy = evictionPolicy;
     }
 
     /**
-     * The total number of objects destroyed by the evictor associated with this
-     * pool over the lifetime of the pool.
-     * @return the evictor destroyed object count
+     * Sets the eviction policy.
+     *
+     * @param className Eviction policy class name.
+     * @param classLoader Load the class from this class loader.
      */
-    public final long getDestroyedByEvictorCount() {
-        return destroyedByEvictorCount.get();
+    @SuppressWarnings("unchecked")
+    private void setEvictionPolicy(final String className, final ClassLoader classLoader)
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+        final Class<?> clazz = Class.forName(className, true, classLoader);
+        final Object policy = clazz.getConstructor().newInstance();
+        this.evictionPolicy = (EvictionPolicy<T>) policy;
     }
 
     /**
-     * The total number of objects destroyed by this pool as a result of failing
-     * validation during {@code borrowObject()} over the lifetime of the
-     * pool.
-     * @return validation destroyed object count
+     * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to
+     * load the class using the thread context class loader. If that fails, the use the class loader for the
+     * {@link EvictionPolicy} interface.
+     *
+     * @param evictionPolicyClassName
+     *            the fully qualified class name of the new eviction policy
+     *
+     * @see #getEvictionPolicyClassName()
+     * @since 2.6.0 If loading the class using the thread context class loader fails, use the class loader for the
+     *        {@link EvictionPolicy} interface.
      */
-    public final long getDestroyedByBorrowValidationCount() {
-        return destroyedByBorrowValidationCount.get();
+    public final void setEvictionPolicyClassName(final String evictionPolicyClassName) {
+        setEvictionPolicyClassName(evictionPolicyClassName, Thread.currentThread().getContextClassLoader());
     }
 
     /**
-     * The mean time objects are active for based on the last {@link
-     * #MEAN_TIMING_STATS_CACHE_SIZE} objects returned to the pool.
-     * @return mean time an object has been checked out from the pool among
-     * recently returned objects
+     * Sets the name of the {@link EvictionPolicy} implementation that is used by this pool. The Pool will attempt to
+     * load the class using the given class loader. If that fails, use the class loader for the {@link EvictionPolicy}
+     * interface.
+     *
+     * @param evictionPolicyClassName
+     *            the fully qualified class name of the new eviction policy
+     * @param classLoader
+     *            the class loader to load the given {@code evictionPolicyClassName}.
+     *
+     * @see #getEvictionPolicyClassName()
+     * @since 2.6.0 If loading the class using the given class loader fails, use the class loader for the
+     *        {@link EvictionPolicy} interface.
      */
-    public final long getMeanActiveTimeMillis() {
-        return activeTimes.getMean();
+    public final void setEvictionPolicyClassName(final String evictionPolicyClassName, final ClassLoader classLoader) {
+        // Getting epClass here and now best matches the caller's environment
+        final Class<?> epClass = EvictionPolicy.class;
+        final ClassLoader epClassLoader = epClass.getClassLoader();
+        try {
+            try {
+                setEvictionPolicy(evictionPolicyClassName, classLoader);
+            } catch (final ClassCastException | ClassNotFoundException e) {
+                setEvictionPolicy(evictionPolicyClassName, epClassLoader);
+            }
+        } catch (final ClassCastException e) {
+            throw new IllegalArgumentException("Class " + evictionPolicyClassName + " from class loaders [" +
+                    classLoader + ", " + epClassLoader + "] do not implement " + EVICTION_POLICY_TYPE_NAME);
+        } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException |
+                InvocationTargetException | NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                    "Unable to create " + EVICTION_POLICY_TYPE_NAME + " instance of type " + evictionPolicyClassName,
+                    e);
+        }
     }
 
     /**
-     * The mean time objects are idle for based on the last {@link
-     * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool.
-     * @return mean time an object has been idle in the pool among recently
-     * borrowed objects
+     * Sets the timeout that will be used when waiting for the Evictor to shutdown if this pool is closed and it is the
+     * only pool still using the the value for the Evictor.
+     *
+     * @param evictorShutdownTimeout the timeout in milliseconds that will be used while waiting for the Evictor
+     *                                     to shut down.
+     * @since 2.10.0
      */
-    public final long getMeanIdleTimeMillis() {
-        return idleTimes.getMean();
+    public final void setEvictorShutdownTimeout(final Duration evictorShutdownTimeout) {
+        this.evictorShutdownTimeoutDuration = PoolImplUtils.nonNull(evictorShutdownTimeout, BaseObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT);
     }
 
     /**
-     * The mean time threads wait to borrow an object based on the last {@link
-     * #MEAN_TIMING_STATS_CACHE_SIZE} objects borrowed from the pool.
-     * @return mean time in milliseconds that a recently served thread has had
-     * to wait to borrow an object from the pool
+     * Sets the timeout that will be used when waiting for the Evictor to shutdown if this pool is closed and it is the
+     * only pool still using the the value for the Evictor.
+     *
+     * @param evictorShutdownTimeoutMillis the timeout in milliseconds that will be used while waiting for the Evictor
+     *                                     to shut down.
+     * @deprecated Use {@link #setEvictorShutdownTimeout(Duration)}.
      */
-    public final long getMeanBorrowWaitTimeMillis() {
-        return waitTimes.getMean();
+    @Deprecated
+    public final void setEvictorShutdownTimeoutMillis(final long evictorShutdownTimeoutMillis) {
+        setEvictorShutdownTimeout(Duration.ofMillis(evictorShutdownTimeoutMillis));
     }
 
     /**
-     * The maximum time a thread has waited to borrow objects from the pool.
-     * @return maximum wait time in milliseconds since the pool was created
+     * Sets whether the pool has LIFO (last in, first out) behavior with
+     * respect to idle objects - always returning the most recently used object
+     * from the pool, or as a FIFO (first in, first out) queue, where the pool
+     * always returns the oldest object in the idle object pool.
+     *
+     * @param lifo  {@code true} if the pool is to be configured with LIFO
+     *              behavior or {@code false} if the pool is to be
+     *              configured with FIFO behavior
+     *
+     * @see #getLifo()
      */
-    public final long getMaxBorrowWaitTimeMillis() {
-        return maxBorrowWaitTimeMillis.get();
+    public final void setLifo(final boolean lifo) {
+        this.lifo = lifo;
     }
 
     /**
-     * The number of instances currently idle in this pool.
-     * @return count of instances available for checkout from the pool
+     * Sets the cap on the number of objects that can be allocated by the pool
+     * (checked out to clients, or idle awaiting checkout) at a given time. Use
+     * a negative value for no limit.
+     *
+     * @param maxTotal  The cap on the total number of object instances managed
+     *                  by the pool. Negative values mean that there is no limit
+     *                  to the number of objects allocated by the pool.
+     *
+     * @see #getMaxTotal
      */
-    public abstract int getNumIdle();
+    public final void setMaxTotal(final int maxTotal) {
+        this.maxTotal = maxTotal;
+    }
 
     /**
-     * The listener used (if any) to receive notifications of exceptions
-     * unavoidably swallowed by the pool.
+     * Sets the maximum duration the
+     * {@code borrowObject()} method should block before throwing an
+     * exception when the pool is exhausted and
+     * {@link #getBlockWhenExhausted} is true. When less than 0, the
+     * {@code borrowObject()} method may block indefinitely.
      *
-     * @return The listener or {@code null} for no listener
+     * @param maxWaitDuration the maximum duration
+     *                      {@code borrowObject()} will block or negative
+     *                      for indefinitely.
+     *
+     * @see #getMaxWaitDuration
+     * @see #setBlockWhenExhausted
+     * @since 2.11.0
      */
-    public final SwallowedExceptionListener getSwallowedExceptionListener() {
-        return swallowedExceptionListener;
+    public final void setMaxWait(final Duration maxWaitDuration) {
+        this.maxWaitDuration = PoolImplUtils.nonNull(maxWaitDuration, BaseObjectPoolConfig.DEFAULT_MAX_WAIT);
     }
 
     /**
-     * The listener used (if any) to receive notifications of exceptions
-     * unavoidably swallowed by the pool.
+     * Sets the maximum amount of time (in milliseconds) the
+     * {@code borrowObject()} method should block before throwing an
+     * exception when the pool is exhausted and
+     * {@link #getBlockWhenExhausted} is true. When less than 0, the
+     * {@code borrowObject()} method may block indefinitely.
      *
-     * @param swallowedExceptionListener    The listener or {@code null}
-     *                                      for no listener
+     * @param maxWaitMillis the maximum number of milliseconds
+     *                      {@code borrowObject()} will block or negative
+     *                      for indefinitely.
+     *
+     * @see #getMaxWaitDuration
+     * @see #setBlockWhenExhausted
+     * @deprecated Use {@link #setMaxWait}.
      */
-    public final void setSwallowedExceptionListener(
-            final SwallowedExceptionListener swallowedExceptionListener) {
-        this.swallowedExceptionListener = swallowedExceptionListener;
+    @Deprecated
+    public final void setMaxWaitMillis(final long maxWaitMillis) {
+        setMaxWait(Duration.ofMillis(maxWaitMillis));
     }
 
     /**
-     * Swallows an exception and notifies the configured listener for swallowed
-     * exceptions queue.
+     * Sets whether to include statistics in exception messages.
+     * <p>
+     * Statistics may not accurately reflect snapshot state at the time of the exception because we do not want to lock the pool when gathering this
+     * information.
+     * </p>
      *
-     * @param swallowException exception to be swallowed
+     * @param messagesDetails whether to include statistics in exception messages.
+     * @since 2.11.0
      */
-    final void swallowException(final Exception swallowException) {
-        final SwallowedExceptionListener listener = getSwallowedExceptionListener();
-
-        if (listener == null) {
-            return;
-        }
-
-        try {
-            listener.onSwallowException(swallowException);
-        } catch (final VirtualMachineError e) {
-            throw e;
-        } catch (final Throwable t) {
-            // Ignore. Enjoy the irony.
-        }
+    public void setMessagesStatistics(final boolean messagesDetails) {
+        this.messageStatistics = messagesDetails;
     }
 
     /**
-     * Updates statistics after an object is borrowed from the pool.
-     * @param p object borrowed from the pool
-     * @param waitTime time (in milliseconds) that the borrowing thread had to wait
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleTime
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction
+     *
+     * @see #getMinEvictableIdleTime
+     * @see #setTimeBetweenEvictionRuns
+     * @since 2.11.0
      */
-    final void updateStatsBorrow(final PooledObject<T> p, final long waitTime) {
-        borrowedCount.incrementAndGet();
-        idleTimes.add(p.getIdleTimeMillis());
-        waitTimes.add(waitTime);
-
-        // lock-free optimistic-locking maximum
-        long currentMax;
-        do {
-            currentMax = maxBorrowWaitTimeMillis.get();
-            if (currentMax >= waitTime) {
-                break;
-            }
-        } while (!maxBorrowWaitTimeMillis.compareAndSet(currentMax, waitTime));
+    public final void setMinEvictableIdle(final Duration minEvictableIdleTime) {
+        this.minEvictableIdleDuration = PoolImplUtils.nonNull(minEvictableIdleTime, BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION);
     }
 
     /**
-     * Updates statistics after an object is returned to the pool.
-     * @param activeTime the amount of time (in milliseconds) that the returning
-     * object was checked out
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleTime
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction
+     *
+     * @see #getMinEvictableIdleTime
+     * @see #setTimeBetweenEvictionRuns
+     * @since 2.10.0
+     * @deprecated Use {@link #setMinEvictableIdle(Duration)}.
      */
-    final void updateStatsReturn(final long activeTime) {
-        returnedCount.incrementAndGet();
-        activeTimes.add(activeTime);
+    @Deprecated
+    public final void setMinEvictableIdleTime(final Duration minEvictableIdleTime) {
+        this.minEvictableIdleDuration = PoolImplUtils.nonNull(minEvictableIdleTime, BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION);
     }
 
     /**
-     * Marks the object as returning to the pool.
-     * @param pooledObject instance to return to the keyed pool
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRunsMillis(long)}). When non-positive,
+     * no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleTimeMillis
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction
+     *
+     * @see #getMinEvictableIdleTimeMillis
+     * @see #setTimeBetweenEvictionRunsMillis
+     * @deprecated Use {@link #setMinEvictableIdleTime(Duration)}.
      */
-    protected void markReturningState(final PooledObject<T> pooledObject) {
-        synchronized(pooledObject) {
-            final PooledObjectState state = pooledObject.getState();
-            if (state != PooledObjectState.ALLOCATED) {
-                throw new IllegalStateException(
-                        "Object has already been returned to this pool or is invalid");
-            }
-            pooledObject.markReturning(); // Keep from being marked abandoned
-        }
+    @Deprecated
+    public final void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
+        setMinEvictableIdleTime(Duration.ofMillis(minEvictableIdleTimeMillis));
     }
 
     /**
-     * Unregisters this pool's MBean.
+     * Sets the maximum number of objects to examine during each run (if any)
+     * of the idle object evictor thread. When positive, the number of tests
+     * performed for a run will be the minimum of the configured value and the
+     * number of idle instances in the pool. When negative, the number of tests
+     * performed will be <code>ceil({@link #getNumIdle}/
+     * abs({@link #getNumTestsPerEvictionRun}))</code> which means that when the
+     * value is {@code -n} roughly one nth of the idle objects will be
+     * tested per run.
+     *
+     * @param numTestsPerEvictionRun
+     *            max number of objects to examine during each evictor run
+     *
+     * @see #getNumTestsPerEvictionRun
+     * @see #setTimeBetweenEvictionRunsMillis
      */
-    final void jmxUnregister() {
-        if (objectName != null) {
-            try {
-                ManagementFactory.getPlatformMBeanServer().unregisterMBean(
-                        objectName);
-            } catch (final MBeanRegistrationException | InstanceNotFoundException e) {
-                swallowException(e);
-            }
-        }
+    public final void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
+        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
     }
 
     /**
-     * Registers the pool with the platform MBean server.
-     * The registered name will be
-     * {@code jmxNameBase + jmxNamePrefix + i} where i is the least
-     * integer greater than or equal to 1 such that the name is not already
-     * registered. Swallows MBeanRegistrationException, NotCompliantMBeanException
-     * returning null.
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}),
+     * with the extra condition that at least {@code minIdle} object
+     * instances remain in the pool. This setting is overridden by
+     * {@link #getMinEvictableIdleTime} (that is, if
+     * {@link #getMinEvictableIdleTime} is positive, then
+     * {@link #getSoftMinEvictableIdleTime} is ignored).
      *
-     * @param config Pool configuration
-     * @param jmxNameBase default base JMX name for this pool
-     * @param jmxNamePrefix name prefix
-     * @return registered ObjectName, null if registration fails
+     * @param softMinEvictableIdleTime
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction if minIdle instances are
+     *            available
+     *
+     * @see #getSoftMinEvictableIdleTimeMillis
+     * @since 2.11.0
      */
-    private ObjectName jmxRegister(final BaseObjectPoolConfig<T> config,
-            final String jmxNameBase, String jmxNamePrefix) {
-        ObjectName newObjectName = null;
-        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-        int i = 1;
-        boolean registered = false;
-        String base = config.getJmxNameBase();
-        if (base == null) {
-            base = jmxNameBase;
-        }
-        while (!registered) {
-            try {
-                ObjectName objName;
-                // Skip the numeric suffix for the first pool in case there is
-                // only one so the names are cleaner.
-                if (i == 1) {
-                    objName = new ObjectName(base + jmxNamePrefix);
-                } else {
-                    objName = new ObjectName(base + jmxNamePrefix + i);
-                }
-                mbs.registerMBean(this, objName);
-                newObjectName = objName;
-                registered = true;
-            } catch (final MalformedObjectNameException e) {
-                if (BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX.equals(
-                        jmxNamePrefix) && jmxNameBase.equals(base)) {
-                    // Shouldn't happen. Skip registration if it does.
-                    registered = true;
-                } else {
-                    // Must be an invalid name. Use the defaults instead.
-                    jmxNamePrefix =
-                            BaseObjectPoolConfig.DEFAULT_JMX_NAME_PREFIX;
-                    base = jmxNameBase;
-                }
-            } catch (final InstanceAlreadyExistsException e) {
-                // Increment the index and try again
-                i++;
-            } catch (final MBeanRegistrationException | NotCompliantMBeanException e) {
-                // Shouldn't happen. Skip registration if it does.
-                registered = true;
-            }
-        }
-        return newObjectName;
+    public final void setSoftMinEvictableIdle(final Duration softMinEvictableIdleTime) {
+        this.softMinEvictableIdleDuration = PoolImplUtils.nonNull(softMinEvictableIdleTime, BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION);
     }
 
     /**
-     * Gets the stack trace of an exception as a string.
-     * @param e exception to trace
-     * @return exception stack trace as a string
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRuns(Duration)}),
+     * with the extra condition that at least {@code minIdle} object
+     * instances remain in the pool. This setting is overridden by
+     * {@link #getMinEvictableIdleTime} (that is, if
+     * {@link #getMinEvictableIdleTime} is positive, then
+     * {@link #getSoftMinEvictableIdleTime} is ignored).
+     *
+     * @param softMinEvictableIdleTime
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction if minIdle instances are
+     *            available
+     *
+     * @see #getSoftMinEvictableIdleTimeMillis
+     * @since 2.10.0
+     * @deprecated Use {@link #setSoftMinEvictableIdle(Duration)}.
      */
-    private String getStackTrace(final Exception e) {
-        // Need the exception in string form to prevent the retention of
-        // references to classes in the stack trace that could trigger a memory
-        // leak in a container environment.
-        final Writer w = new StringWriter();
-        final PrintWriter pw = new PrintWriter(w);
-        e.printStackTrace(pw);
-        return w.toString();
+    @Deprecated
+    public final void setSoftMinEvictableIdleTime(final Duration softMinEvictableIdleTime) {
+        this.softMinEvictableIdleDuration = PoolImplUtils.nonNull(softMinEvictableIdleTime, BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION);
     }
 
-    // Inner classes
-
     /**
-     * The idle object evictor {@link TimerTask}.
+     * Sets the minimum amount of time an object may sit idle in the pool
+     * before it is eligible for eviction by the idle object evictor (if any -
+     * see {@link #setTimeBetweenEvictionRunsMillis(long)}),
+     * with the extra condition that at least {@code minIdle} object
+     * instances remain in the pool. This setting is overridden by
+     * {@link #getMinEvictableIdleTimeMillis} (that is, if
+     * {@link #getMinEvictableIdleTimeMillis} is positive, then
+     * {@link #getSoftMinEvictableIdleTimeMillis} is ignored).
      *
-     * @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis
+     * @param softMinEvictableIdleTimeMillis
+     *            minimum amount of time an object may sit idle in the pool
+     *            before it is eligible for eviction if minIdle instances are
+     *            available
+     *
+     * @see #getSoftMinEvictableIdleTimeMillis
+     * @deprecated Use {@link #setSoftMinEvictableIdle(Duration)}.
      */
-    class Evictor implements Runnable {
-
-        private ScheduledFuture<?> scheduledFuture;
-
-        /**
-         * 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.
-         */
-        @Override
-        public void run() {
-            final ClassLoader savedClassLoader =
-                    Thread.currentThread().getContextClassLoader();
-            try {
-                if (factoryClassLoader != null) {
-                    // Set the class loader for the factory
-                    final ClassLoader cl = factoryClassLoader.get();
-                    if (cl == null) {
-                        // The pool has been dereferenced and the class loader
-                        // GC'd. Cancel this timer so the pool can be GC'd as
-                        // well.
-                        cancel();
-                        return;
-                    }
-                    Thread.currentThread().setContextClassLoader(cl);
-                }
-
-                // Evict from the pool
-                try {
-                    evict();
-                } catch(final Exception e) {
-                    swallowException(e);
-                } catch(final 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 {
-                    ensureMinIdle();
-                } catch (final Exception e) {
-                    swallowException(e);
-                }
-            } finally {
-                // Restore the previous CCL
-                Thread.currentThread().setContextClassLoader(savedClassLoader);
-            }
-        }
-
-
-        /**
-         * Sets the scheduled future.
-         *
-         * @param scheduledFuture the scheduled future.
-         */
-        void setScheduledFuture(final ScheduledFuture<?> scheduledFuture) {
-            this.scheduledFuture = scheduledFuture;
-        }
+    @Deprecated
+    public final void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
+        setSoftMinEvictableIdleTime(Duration.ofMillis(softMinEvictableIdleTimeMillis));
+    }
 
+    /**
+     * The listener used (if any) to receive notifications of exceptions
+     * unavoidably swallowed by the pool.
+     *
+     * @param swallowedExceptionListener    The listener or {@code null}
+     *                                      for no listener
+     */
+    public final void setSwallowedExceptionListener(
+            final SwallowedExceptionListener swallowedExceptionListener) {
+        this.swallowedExceptionListener = swallowedExceptionListener;
+    }
 
-        /**
-         * Cancels the scheduled future.
-         */
-        void cancel() {
-            scheduledFuture.cancel(false);
-        }
+    /**
+     * Sets whether objects borrowed from the pool will be validated before
+     * being returned from the {@code borrowObject()} method. Validation is
+     * performed by the {@code validateObject()} method of the factory
+     * associated with the pool. If the object fails to validate, it will be
+     * removed from the pool and destroyed, and a new attempt will be made to
+     * borrow an object from the pool.
+     *
+     * @param testOnBorrow  {@code true} if objects should be validated
+     *                      before being returned from the
+     *                      {@code borrowObject()} method
+     *
+     * @see #getTestOnBorrow
+     */
+    public final void setTestOnBorrow(final boolean testOnBorrow) {
+        this.testOnBorrow = testOnBorrow;
+    }
 
+    /**
+     * Sets whether objects created for the pool will be validated before
+     * being returned from the {@code borrowObject()} method. Validation is
+     * performed by the {@code validateObject()} method of the factory
+     * associated with the pool. If the object fails to validate, then
+     * {@code borrowObject()} will fail.
+     *
+     * @param testOnCreate  {@code true} if newly created objects should be
+     *                      validated before being returned from the
+     *                      {@code borrowObject()} method
+     *
+     * @see #getTestOnCreate
+     *
+     * @since 2.2
+     */
+    public final void setTestOnCreate(final boolean testOnCreate) {
+        this.testOnCreate = testOnCreate;
     }
 
     /**
-     * Maintains a cache of values for a single metric and reports
-     * statistics on the cached values.
+     * Sets whether objects borrowed from the pool will be validated when
+     * they are returned to the pool via the {@code returnObject()} method.
+     * Validation is performed by the {@code validateObject()} method of
+     * the factory associated with the pool. Returning objects that fail validation
+     * are destroyed rather then being returned the pool.
+     *
+     * @param testOnReturn {@code true} if objects are validated on
+     *                     return to the pool via the
+     *                     {@code returnObject()} method
+     *
+     * @see #getTestOnReturn
      */
-    private class StatsStore {
+    public final void setTestOnReturn(final boolean testOnReturn) {
+        this.testOnReturn = testOnReturn;
+    }
 
-        private final AtomicLong values[];
-        private final int size;
-        private int index;
+    /**
+     * Sets whether objects sitting idle in the pool will be validated by the
+     * idle object evictor (if any - see
+     * {@link #setTimeBetweenEvictionRuns(Duration)}). Validation is performed
+     * by the {@code validateObject()} method of the factory associated
+     * with the pool. If the object fails to validate, it will be removed from
+     * the pool and destroyed.  Note that setting this property has no effect
+     * unless the idle object evictor is enabled by setting
+     * {@code timeBetweenEvictionRunsMillis} to a positive value.
+     *
+     * @param testWhileIdle
+     *            {@code true} so objects will be validated by the evictor
+     *
+     * @see #getTestWhileIdle
+     * @see #setTimeBetweenEvictionRuns
+     */
+    public final void setTestWhileIdle(final boolean testWhileIdle) {
+        this.testWhileIdle = testWhileIdle;
+    }
 
-        /**
-         * Create a StatsStore with the given cache size.
-         *
-         * @param size number of values to maintain in the cache.
-         */
-        public StatsStore(final int size) {
-            this.size = size;
-            values = new AtomicLong[size];
-            for (int i = 0; i < size; i++) {
-                values[i] = new AtomicLong(-1);
-            }
-        }
+    /**
+     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread.
+     * <ul>
+     * <li>When positive, the idle object evictor thread starts.</li>
+     * <li>When non-positive, no idle object evictor thread runs.</li>
+     * </ul>
+     *
+     * @param timeBetweenEvictionRuns
+     *            duration to sleep between evictor runs
+     *
+     * @see #getDurationBetweenEvictionRuns()
+     * @since 2.10.0
+     */
+    public final void setTimeBetweenEvictionRuns(final Duration timeBetweenEvictionRuns) {
+        this.durationBetweenEvictionRuns = PoolImplUtils.nonNull(timeBetweenEvictionRuns, BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS);
+        startEvictor(this.durationBetweenEvictionRuns);
+    }
 
-        /**
-         * Adds a value to the cache.  If the cache is full, one of the
-         * existing values is replaced by the new value.
-         *
-         * @param value new value to add to the cache.
-         */
-        public synchronized void add(final long value) {
-            values[index].set(value);
-            index++;
-            if (index == size) {
-                index = 0;
-            }
-        }
+    /**
+     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread.
+     * <ul>
+     * <li>When positive, the idle object evictor thread starts.</li>
+     * <li>When non-positive, no idle object evictor thread runs.</li>
+     * </ul>
+     *
+     * @param timeBetweenEvictionRunsMillis
+     *            number of milliseconds to sleep between evictor runs
+     *
+     * @see #getDurationBetweenEvictionRuns()
+     * @deprecated Use {@link #setTimeBetweenEvictionRuns(Duration)}.
+     */
+    @Deprecated
+    public final void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis));
+    }
 
-        /**
-         * Returns the mean of the cached values.
-         *
-         * @return the mean of the cache, truncated to long
-         */
-        public long getMean() {
-            double result = 0;
-            int counter = 0;
-            for (int i = 0; i < size; i++) {
-                final long value = values[i].get();
-                if (value != -1) {
-                    counter++;
-                    result = result * ((counter - 1) / (double) counter) +
-                            value/(double) counter;
+    /**
+     * <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 void startEvictor(final Duration delay) {
+        synchronized (evictionLock) {
+            final boolean isPositiverDelay = PoolImplUtils.isPositive(delay);
+            if (evictor == null) { // Starting evictor for the first time or after a cancel
+                if (isPositiverDelay) { // Starting new evictor
+                    evictor = new Evictor();
+                    EvictionTimer.schedule(evictor, delay, delay);
+                }
+            } else if (isPositiverDelay) { // Stop or restart of existing evictor: Restart
+                synchronized (EvictionTimer.class) { // Ensure no cancel can happen between cancel / schedule calls
+                    EvictionTimer.cancel(evictor, evictorShutdownTimeoutDuration, true);
+                    evictor = null;
+                    evictionIterator = null;
+                    evictor = new Evictor();
+                    EvictionTimer.schedule(evictor, delay, delay);
                 }
+            } else { // Stopping evictor
+                EvictionTimer.cancel(evictor, evictorShutdownTimeoutDuration, false);
             }
-            return (long) result;
-        }
-
-        @Override
-        public String toString() {
-            final StringBuilder builder = new StringBuilder();
-            builder.append("StatsStore [values=");
-            builder.append(Arrays.toString(values));
-            builder.append(", size=");
-            builder.append(size);
-            builder.append(", index=");
-            builder.append(index);
-            builder.append("]");
-            return builder.toString();
         }
     }
 
     /**
-     * The idle object eviction iterator. Holds a reference to the idle objects.
+     * Stops the evictor.
      */
-    class EvictionIterator implements Iterator<PooledObject<T>> {
-
-        private final Deque<PooledObject<T>> idleObjects;
-        private final Iterator<PooledObject<T>> idleObjectIterator;
-
-        /**
-         * Create an EvictionIterator for the provided idle instance deque.
-         * @param idleObjects underlying deque
-         */
-        EvictionIterator(final Deque<PooledObject<T>> idleObjects) {
-            this.idleObjects = idleObjects;
-
-            if (getLifo()) {
-                idleObjectIterator = idleObjects.descendingIterator();
-            } else {
-                idleObjectIterator = idleObjects.iterator();
-            }
-        }
-
-        /**
-         * Returns the idle object deque referenced by this iterator.
-         * @return the idle object deque
-         */
-        public Deque<PooledObject<T>> getIdleObjects() {
-            return idleObjects;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean hasNext() {
-            return idleObjectIterator.hasNext();
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public PooledObject<T> next() {
-            return idleObjectIterator.next();
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void remove() {
-            idleObjectIterator.remove();
-        }
-
+    void stopEvictor() {
+        startEvictor(Duration.ofMillis(-1L));
     }
 
     /**
-     * Wrapper for objects under management by the pool.
-     *
-     * GenericObjectPool and GenericKeyedObjectPool maintain references to all
-     * objects under management using maps keyed on the objects. This wrapper
-     * class ensures that objects can work as hash keys.
+     * Swallows an exception and notifies the configured listener for swallowed
+     * exceptions queue.
      *
-     * @param <T> type of objects in the pool
+     * @param swallowException exception to be swallowed
      */
-    static class IdentityWrapper<T> {
-        /** Wrapped object */
-        private final T instance;
-
-        /**
-         * Create a wrapper for an instance.
-         *
-         * @param instance object to wrap
-         */
-        public IdentityWrapper(final T instance) {
-            this.instance = instance;
-        }
-
-        @Override
-        public int hashCode() {
-            return System.identityHashCode(instance);
-        }
-
-        @Override
-        @SuppressWarnings("rawtypes")
-        public boolean equals(final Object other) {
-            return  other instanceof IdentityWrapper &&
-                    ((IdentityWrapper) other).instance == instance;
-        }
+    final void swallowException(final Exception swallowException) {
+        final SwallowedExceptionListener listener = getSwallowedExceptionListener();
 
-        /**
-         * @return the wrapped object
-         */
-        public T getObject() {
-            return instance;
+        if (listener == null) {
+            return;
         }
 
-        @Override
-        public String toString() {
-            final StringBuilder builder = new StringBuilder();
-            builder.append("IdentityWrapper [instance=");
-            builder.append(instance);
-            builder.append("]");
-            return builder.toString();
+        try {
+            listener.onSwallowException(swallowException);
+        } catch (final VirtualMachineError e) {
+            throw e;
+        } catch (final Throwable t) {
+            // Ignore. Enjoy the irony.
         }
     }
 
@@ -1374,8 +1830,8 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
         builder.append(maxTotal);
         builder.append(", blockWhenExhausted=");
         builder.append(blockWhenExhausted);
-        builder.append(", maxWaitMillis=");
-        builder.append(maxWaitMillis);
+        builder.append(", maxWaitDuration=");
+        builder.append(maxWaitDuration);
         builder.append(", lifo=");
         builder.append(lifo);
         builder.append(", fairness=");
@@ -1388,14 +1844,14 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
         builder.append(testOnReturn);
         builder.append(", testWhileIdle=");
         builder.append(testWhileIdle);
-        builder.append(", timeBetweenEvictionRunsMillis=");
-        builder.append(timeBetweenEvictionRunsMillis);
+        builder.append(", durationBetweenEvictionRuns=");
+        builder.append(durationBetweenEvictionRuns);
         builder.append(", numTestsPerEvictionRun=");
         builder.append(numTestsPerEvictionRun);
-        builder.append(", minEvictableIdleTimeMillis=");
-        builder.append(minEvictableIdleTimeMillis);
-        builder.append(", softMinEvictableIdleTimeMillis=");
-        builder.append(softMinEvictableIdleTimeMillis);
+        builder.append(", minEvictableIdleTimeDuration=");
+        builder.append(minEvictableIdleDuration);
+        builder.append(", softMinEvictableIdleTimeDuration=");
+        builder.append(softMinEvictableIdleDuration);
         builder.append(", evictionPolicy=");
         builder.append(evictionPolicy);
         builder.append(", closeLock=");
@@ -1432,11 +1888,46 @@ public abstract class BaseGenericObjectPool<T> extends BaseObject {
         builder.append(idleTimes);
         builder.append(", waitTimes=");
         builder.append(waitTimes);
-        builder.append(", maxBorrowWaitTimeMillis=");
-        builder.append(maxBorrowWaitTimeMillis);
+        builder.append(", maxBorrowWaitDuration=");
+        builder.append(maxBorrowWaitDuration);
         builder.append(", swallowedExceptionListener=");
         builder.append(swallowedExceptionListener);
     }
 
+    /**
+     * Updates statistics after an object is borrowed from the pool.
+     *
+     * @param p object borrowed from the pool
+     * @param waitDuration that the borrowing thread had to wait
+     */
+    final void updateStatsBorrow(final PooledObject<T> p, final Duration waitDuration) {
+        borrowedCount.incrementAndGet();
+        idleTimes.add(p.getIdleDuration());
+        waitTimes.add(waitDuration);
+
+        // lock-free optimistic-locking maximum
+        Duration currentMaxDuration;
+        do {
+            currentMaxDuration = maxBorrowWaitDuration.get();
+//            if (currentMaxDuration >= waitDuration) {
+//                break;
+//            }
+            if (currentMaxDuration.compareTo(waitDuration) >= 0) {
+                break;
+            }
+        } while (!maxBorrowWaitDuration.compareAndSet(currentMaxDuration, waitDuration));
+    }
+
+    /**
+     * Updates statistics after an object is returned to the pool.
+     *
+     * @param activeTime the amount of time (in milliseconds) that the returning
+     * object was checked out
+     */
+    final void updateStatsReturn(final Duration activeTime) {
+        returnedCount.incrementAndGet();
+        activeTimes.add(activeTime);
+    }
+
 
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/BaseObjectPoolConfig.java b/java/org/apache/tomcat/dbcp/pool2/impl/BaseObjectPoolConfig.java
index 8e068f2..eee987a 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/BaseObjectPoolConfig.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/BaseObjectPoolConfig.java
@@ -16,6 +16,8 @@
  */
 package org.apache.tomcat.dbcp.pool2.impl;
 
+import java.time.Duration;
+
 import org.apache.tomcat.dbcp.pool2.BaseObject;
 
 /**
@@ -47,36 +49,104 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
 
     /**
      * The default value for the {@code maxWait} configuration attribute.
-     * @see GenericObjectPool#getMaxWaitMillis()
-     * @see GenericKeyedObjectPool#getMaxWaitMillis()
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @deprecated Use {@link #DEFAULT_MAX_WAIT}.
      */
+    @Deprecated
     public static final long DEFAULT_MAX_WAIT_MILLIS = -1L;
 
     /**
-     * The default value for the {@code minEvictableIdleTimeMillis}
+     * The default value for the {@code maxWait} configuration attribute.
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @since 2.10.0
+     */
+    public static final Duration DEFAULT_MAX_WAIT = Duration.ofMillis(DEFAULT_MAX_WAIT_MILLIS);
+
+    /**
+     * The default value for the {@code minEvictableIdleDuration}
+     * configuration attribute.
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @deprecated Use {@link #DEFAULT_MIN_EVICTABLE_IDLE_TIME}.
+     */
+    @Deprecated
+    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L;
+
+    /**
+     * The default value for the {@code minEvictableIdleDuration}
+     * configuration attribute.
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @since 2.11.0
+     */
+    public static final Duration DEFAULT_MIN_EVICTABLE_IDLE_DURATION =
+            Duration.ofMillis(DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
+
+    /**
+     * The default value for the {@code minEvictableIdleDuration}
      * configuration attribute.
-     * @see GenericObjectPool#getMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #DEFAULT_MIN_EVICTABLE_IDLE_DURATION}.
      */
-    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS =
-            1000L * 60L * 30L;
+    @Deprecated
+    public static final Duration DEFAULT_MIN_EVICTABLE_IDLE_TIME =
+            Duration.ofMillis(DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
 
     /**
-     * The default value for the {@code softMinEvictableIdleTimeMillis}
+     * The default value for the {@code softMinEvictableIdleTime}
      * configuration attribute.
-     * @see GenericObjectPool#getSoftMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @deprecated Use {@link #DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME}.
      */
+    @Deprecated
     public static final long DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1;
 
     /**
-     * The default value for {@code evictorShutdownTimeoutMillis} configuration
+     * The default value for the {@code softMinEvictableIdleTime}
+     * configuration attribute.
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION}.
+     */
+    @Deprecated
+    public static final Duration DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME =
+            Duration.ofMillis(DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
+
+    /**
+     * The default value for the {@code softMinEvictableIdleTime}
+     * configuration attribute.
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @since 2.11.0
+     */
+    public static final Duration DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION =
+            Duration.ofMillis(DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
+
+    /**
+     * The default value for {@code evictorShutdownTimeout} configuration
+     * attribute.
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @deprecated Use {@link #DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT}.
+     */
+    @Deprecated
+    public static final long DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS = 10L * 1000L;
+
+    /**
+     * The default value for {@code evictorShutdownTimeout} configuration
      * attribute.
-     * @see GenericObjectPool#getEvictorShutdownTimeoutMillis()
-     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutMillis()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @since 2.10.0
      */
-    public static final long DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS =
-            10L * 1000L;
+    public static final Duration DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT =
+            Duration.ofMillis(DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS);
 
     /**
      * The default value for the {@code numTestsPerEvictionRun} configuration
@@ -117,14 +187,25 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
     public static final boolean DEFAULT_TEST_WHILE_IDLE = false;
 
     /**
-     * The default value for the {@code timeBetweenEvictionRunsMillis}
+     * The default value for the {@code timeBetweenEvictionRuns}
      * configuration attribute.
-     * @see GenericObjectPool#getTimeBetweenEvictionRunsMillis()
-     * @see GenericKeyedObjectPool#getTimeBetweenEvictionRunsMillis()
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @deprecated Use {@link #DEFAULT_TIME_BETWEEN_EVICTION_RUNS}.
      */
+    @Deprecated
     public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L;
 
     /**
+     * The default value for the {@code timeBetweenEvictionRuns}
+     * configuration attribute.
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     */
+    public static final Duration DEFAULT_TIME_BETWEEN_EVICTION_RUNS = Duration
+            .ofMillis(DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
+
+    /**
      * The default value for the {@code blockWhenExhausted} configuration
      * attribute.
      * @see GenericObjectPool#getBlockWhenExhausted()
@@ -167,21 +248,17 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
 
     private boolean fairness = DEFAULT_FAIRNESS;
 
-    private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
+    private Duration maxWaitDuration = DEFAULT_MAX_WAIT;
 
-    private long minEvictableIdleTimeMillis =
-            DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration minEvictableIdleDuration = DEFAULT_MIN_EVICTABLE_IDLE_TIME;
 
-    private long evictorShutdownTimeoutMillis =
-            DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
+    private Duration evictorShutdownTimeoutDuration = DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT;
 
-    private long softMinEvictableIdleTimeMillis =
-            DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration softMinEvictableIdleDuration = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME;
 
-    private int numTestsPerEvictionRun =
-            DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
+    private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
 
-    private EvictionPolicy<T> evictionPolicy = null; // Only 2.6.0 applications set this
+    private EvictionPolicy<T> evictionPolicy; // Only 2.6.0 applications set this
 
     private String evictionPolicyClassName = DEFAULT_EVICTION_POLICY_CLASS_NAME;
 
@@ -193,8 +270,7 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
 
     private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
 
-    private long timeBetweenEvictionRunsMillis =
-            DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
+    private Duration durationBetweenEvictionRuns = DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
 
     private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;
 
@@ -207,241 +283,321 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
 
 
     /**
-     * Get the value for the {@code lifo} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code blockWhenExhausted} configuration attribute
+     * for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code lifo} for this configuration
-     *          instance
+     * @return  The current setting of {@code blockWhenExhausted} for this
+     *          configuration instance
      *
-     * @see GenericObjectPool#getLifo()
-     * @see GenericKeyedObjectPool#getLifo()
+     * @see GenericObjectPool#getBlockWhenExhausted()
+     * @see GenericKeyedObjectPool#getBlockWhenExhausted()
      */
-    public boolean getLifo() {
-        return lifo;
+    public boolean getBlockWhenExhausted() {
+        return blockWhenExhausted;
     }
 
     /**
-     * Get the value for the {@code fairness} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code timeBetweenEvictionRuns} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code fairness} for this configuration
-     *          instance
+     * @return  The current setting of {@code timeBetweenEvictionRuns} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getFairness()
-     * @see GenericKeyedObjectPool#getFairness()
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @since 2.11.0
      */
-    public boolean getFairness() {
-        return fairness;
+    public Duration getDurationBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
     }
 
     /**
-     * Set the value for the {@code lifo} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code evictionPolicyClass} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @param lifo The new setting of {@code lifo}
-     *        for this configuration instance
+     * @return  The current setting of {@code evictionPolicyClass} for this
+     *          configuration instance
      *
-     * @see GenericObjectPool#getLifo()
-     * @see GenericKeyedObjectPool#getLifo()
+     * @see GenericObjectPool#getEvictionPolicy()
+     * @see GenericKeyedObjectPool#getEvictionPolicy()
+     * @since 2.6.0
      */
-    public void setLifo(final boolean lifo) {
-        this.lifo = lifo;
+    public EvictionPolicy<T> getEvictionPolicy() {
+        return evictionPolicy;
     }
 
     /**
-     * Set the value for the {@code fairness} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code evictionPolicyClassName} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @param fairness The new setting of {@code fairness}
-     *        for this configuration instance
+     * @return  The current setting of {@code evictionPolicyClassName} for this
+     *          configuration instance
      *
-     * @see GenericObjectPool#getFairness()
-     * @see GenericKeyedObjectPool#getFairness()
+     * @see GenericObjectPool#getEvictionPolicyClassName()
+     * @see GenericKeyedObjectPool#getEvictionPolicyClassName()
      */
-    public void setFairness(final boolean fairness) {
-        this.fairness = fairness;
+    public String getEvictionPolicyClassName() {
+        return evictionPolicyClassName;
     }
 
     /**
-     * Get the value for the {@code maxWait} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code evictorShutdownTimeout} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code maxWait} for this
-     *          configuration instance
+     * @return  The current setting of {@code evictorShutdownTimeout} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getMaxWaitMillis()
-     * @see GenericKeyedObjectPool#getMaxWaitMillis()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #getEvictorShutdownTimeoutDuration()}.
      */
-    public long getMaxWaitMillis() {
-        return maxWaitMillis;
+    @Deprecated
+    public Duration getEvictorShutdownTimeout() {
+        return evictorShutdownTimeoutDuration;
     }
 
     /**
-     * Set the value for the {@code maxWait} configuration attribute for pools
-     * created with this configuration instance.
+     * Gets the value for the {@code evictorShutdownTimeout} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @param maxWaitMillis The new setting of {@code maxWaitMillis}
-     *        for this configuration instance
+     * @return  The current setting of {@code evictorShutdownTimeout} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getMaxWaitMillis()
-     * @see GenericKeyedObjectPool#getMaxWaitMillis()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @since 2.11.0
      */
-    public void setMaxWaitMillis(final long maxWaitMillis) {
-        this.maxWaitMillis = maxWaitMillis;
+    public Duration getEvictorShutdownTimeoutDuration() {
+        return evictorShutdownTimeoutDuration;
     }
 
     /**
-     * Get the value for the {@code minEvictableIdleTimeMillis} configuration
+     * Gets the value for the {@code evictorShutdownTimeout} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code minEvictableIdleTimeMillis} for
+     * @return  The current setting of {@code evictorShutdownTimeout} for
      *          this configuration instance
      *
-     * @see GenericObjectPool#getMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @deprecated Use {@link #getEvictorShutdownTimeout()}.
      */
-    public long getMinEvictableIdleTimeMillis() {
-        return minEvictableIdleTimeMillis;
+    @Deprecated
+    public long getEvictorShutdownTimeoutMillis() {
+        return evictorShutdownTimeoutDuration.toMillis();
     }
 
     /**
-     * Set the value for the {@code minEvictableIdleTimeMillis} configuration
-     * attribute for pools created with this configuration instance.
+     * Gets the value for the {@code fairness} configuration attribute for pools
+     * created with this configuration instance.
      *
-     * @param minEvictableIdleTimeMillis The new setting of
-     *        {@code minEvictableIdleTimeMillis} for this configuration instance
+     * @return  The current setting of {@code fairness} for this configuration
+     *          instance
      *
-     * @see GenericObjectPool#getMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()
+     * @see GenericObjectPool#getFairness()
+     * @see GenericKeyedObjectPool#getFairness()
      */
-    public void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
-        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+    public boolean getFairness() {
+        return fairness;
     }
 
     /**
-     * Get the value for the {@code softMinEvictableIdleTimeMillis}
-     * configuration attribute for pools created with this configuration
-     * instance.
+     * Gets the value of the flag that determines if JMX will be enabled for
+     * pools created with this configuration instance.
      *
-     * @return  The current setting of {@code softMinEvictableIdleTimeMillis}
-     *          for this configuration instance
+     * @return  The current setting of {@code jmxEnabled} for this configuration
+     *          instance
+     */
+    public boolean getJmxEnabled() {
+        return jmxEnabled;
+    }
+
+    /**
+     * Gets the value of the JMX name base that will be used as part of the
+     * name assigned to JMX enabled pools created with this configuration
+     * instance. A value of {@code null} means that the pool will define
+     * the JMX name base.
      *
-     * @see GenericObjectPool#getSoftMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()
+     * @return  The current setting of {@code jmxNameBase} for this
+     *          configuration instance
      */
-    public long getSoftMinEvictableIdleTimeMillis() {
-        return softMinEvictableIdleTimeMillis;
+    public String getJmxNameBase() {
+        return jmxNameBase;
     }
 
     /**
-     * Set the value for the {@code softMinEvictableIdleTimeMillis}
-     * configuration attribute for pools created with this configuration
+     * Gets the value of the JMX name prefix that will be used as part of the
+     * name assigned to JMX enabled pools created with this configuration
      * instance.
      *
-     * @param softMinEvictableIdleTimeMillis The new setting of
-     *        {@code softMinEvictableIdleTimeMillis} for this configuration
-     *        instance
+     * @return  The current setting of {@code jmxNamePrefix} for this
+     *          configuration instance
+     */
+    public String getJmxNamePrefix() {
+        return jmxNamePrefix;
+    }
+
+    /**
+     * Gets the value for the {@code lifo} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @return  The current setting of {@code lifo} for this configuration
+     *          instance
      *
-     * @see GenericObjectPool#getSoftMinEvictableIdleTimeMillis()
-     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()
+     * @see GenericObjectPool#getLifo()
+     * @see GenericKeyedObjectPool#getLifo()
      */
-    public void setSoftMinEvictableIdleTimeMillis(
-            final long softMinEvictableIdleTimeMillis) {
-        this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
+    public boolean getLifo() {
+        return lifo;
     }
 
     /**
-     * Get the value for the {@code numTestsPerEvictionRun} configuration
-     * attribute for pools created with this configuration instance.
+     * Gets the value for the {@code maxWait} configuration attribute for pools
+     * created with this configuration instance.
      *
-     * @return  The current setting of {@code numTestsPerEvictionRun} for this
+     * @return  The current setting of {@code maxWait} for this
      *          configuration instance
      *
-     * @see GenericObjectPool#getNumTestsPerEvictionRun()
-     * @see GenericKeyedObjectPool#getNumTestsPerEvictionRun()
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @since 2.11.0
      */
-    public int getNumTestsPerEvictionRun() {
-        return numTestsPerEvictionRun;
+    public Duration getMaxWaitDuration() {
+        return maxWaitDuration;
     }
 
     /**
-     * Set the value for the {@code numTestsPerEvictionRun} configuration
+     * Gets the value for the {@code maxWait} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @return  The current setting of {@code maxWait} for this
+     *          configuration instance
+     *
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @deprecated Use {@link #getMaxWaitDuration()}.
+     */
+    @Deprecated
+    public long getMaxWaitMillis() {
+        return maxWaitDuration.toMillis();
+    }
+
+    /**
+     * Gets the value for the {@code minEvictableIdleTime} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @param numTestsPerEvictionRun The new setting of
-     *        {@code numTestsPerEvictionRun} for this configuration instance
+     * @return  The current setting of {@code minEvictableIdleTime} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getNumTestsPerEvictionRun()
-     * @see GenericKeyedObjectPool#getNumTestsPerEvictionRun()
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @since 2.11.0
      */
-    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
-        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
+    public Duration getMinEvictableIdleDuration() {
+        return minEvictableIdleDuration;
     }
 
     /**
-     * Get the value for the {@code evictorShutdownTimeoutMillis} configuration
+     * Gets the value for the {@code minEvictableIdleTime} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code evictorShutdownTimeoutMillis} for
+     * @return  The current setting of {@code minEvictableIdleTime} for
      *          this configuration instance
      *
-     * @see GenericObjectPool#getEvictorShutdownTimeoutMillis()
-     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutMillis()
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #getMinEvictableIdleDuration()}.
      */
-    public long getEvictorShutdownTimeoutMillis() {
-        return evictorShutdownTimeoutMillis;
+    @Deprecated
+    public Duration getMinEvictableIdleTime() {
+        return minEvictableIdleDuration;
     }
 
     /**
-     * Set the value for the {@code evictorShutdownTimeoutMillis} configuration
+     * Gets the value for the {@code minEvictableIdleTime} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @param evictorShutdownTimeoutMillis The new setting of
-     *        {@code evictorShutdownTimeoutMillis} for this configuration
-     *        instance
+     * @return  The current setting of {@code minEvictableIdleTime} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getEvictorShutdownTimeoutMillis()
-     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutMillis()
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @deprecated Use {@link #getMinEvictableIdleTime()}.
      */
-    public void setEvictorShutdownTimeoutMillis(
-            final long evictorShutdownTimeoutMillis) {
-        this.evictorShutdownTimeoutMillis = evictorShutdownTimeoutMillis;
+    @Deprecated
+    public long getMinEvictableIdleTimeMillis() {
+        return minEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Get the value for the {@code testOnCreate} configuration attribute for
-     * pools created with this configuration instance.
+     * Gets the value for the {@code numTestsPerEvictionRun} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code testOnCreate} for this
+     * @return  The current setting of {@code numTestsPerEvictionRun} for this
      *          configuration instance
      *
-     * @see GenericObjectPool#getTestOnCreate()
-     * @see GenericKeyedObjectPool#getTestOnCreate()
+     * @see GenericObjectPool#getNumTestsPerEvictionRun()
+     * @see GenericKeyedObjectPool#getNumTestsPerEvictionRun()
+     */
+    public int getNumTestsPerEvictionRun() {
+        return numTestsPerEvictionRun;
+    }
+
+    /**
+     * Gets the value for the {@code softMinEvictableIdleTime}
+     * configuration attribute for pools created with this configuration
+     * instance.
      *
-     * @since 2.2
+     * @return  The current setting of {@code softMinEvictableIdleTime}
+     *          for this configuration instance
+     *
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @since 2.11.0
      */
-    public boolean getTestOnCreate() {
-        return testOnCreate;
+    public Duration getSoftMinEvictableIdleDuration() {
+        return softMinEvictableIdleDuration;
     }
 
     /**
-     * Set the value for the {@code testOnCreate} configuration attribute for
-     * pools created with this configuration instance.
+     * Gets the value for the {@code softMinEvictableIdleTime}
+     * configuration attribute for pools created with this configuration
+     * instance.
      *
-     * @param testOnCreate The new setting of {@code testOnCreate}
-     *        for this configuration instance
+     * @return  The current setting of {@code softMinEvictableIdleTime}
+     *          for this configuration instance
      *
-     * @see GenericObjectPool#getTestOnCreate()
-     * @see GenericKeyedObjectPool#getTestOnCreate()
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #getSoftMinEvictableIdleDuration()}.
+     */
+    @Deprecated
+    public Duration getSoftMinEvictableIdleTime() {
+        return softMinEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the value for the {@code softMinEvictableIdleTime}
+     * configuration attribute for pools created with this configuration
+     * instance.
      *
-     * @since 2.2
+     * @return  The current setting of {@code softMinEvictableIdleTime}
+     *          for this configuration instance
+     *
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @deprecated Use {@link #getSoftMinEvictableIdleDuration()}.
      */
-    public void setTestOnCreate(final boolean testOnCreate) {
-        this.testOnCreate = testOnCreate;
+    @Deprecated
+    public long getSoftMinEvictableIdleTimeMillis() {
+        return softMinEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Get the value for the {@code testOnBorrow} configuration attribute for
+     * Gets the value for the {@code testOnBorrow} configuration attribute for
      * pools created with this configuration instance.
      *
      * @return  The current setting of {@code testOnBorrow} for this
@@ -455,21 +611,23 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
     }
 
     /**
-     * Set the value for the {@code testOnBorrow} configuration attribute for
+     * Gets the value for the {@code testOnCreate} configuration attribute for
      * pools created with this configuration instance.
      *
-     * @param testOnBorrow The new setting of {@code testOnBorrow}
-     *        for this configuration instance
+     * @return  The current setting of {@code testOnCreate} for this
+     *          configuration instance
      *
-     * @see GenericObjectPool#getTestOnBorrow()
-     * @see GenericKeyedObjectPool#getTestOnBorrow()
+     * @see GenericObjectPool#getTestOnCreate()
+     * @see GenericKeyedObjectPool#getTestOnCreate()
+     *
+     * @since 2.2
      */
-    public void setTestOnBorrow(final boolean testOnBorrow) {
-        this.testOnBorrow = testOnBorrow;
+    public boolean getTestOnCreate() {
+        return testOnCreate;
     }
 
     /**
-     * Get the value for the {@code testOnReturn} configuration attribute for
+     * Gets the value for the {@code testOnReturn} configuration attribute for
      * pools created with this configuration instance.
      *
      * @return  The current setting of {@code testOnReturn} for this
@@ -483,21 +641,7 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
     }
 
     /**
-     * Set the value for the {@code testOnReturn} configuration attribute for
-     * pools created with this configuration instance.
-     *
-     * @param testOnReturn The new setting of {@code testOnReturn}
-     *        for this configuration instance
-     *
-     * @see GenericObjectPool#getTestOnReturn()
-     * @see GenericKeyedObjectPool#getTestOnReturn()
-     */
-    public void setTestOnReturn(final boolean testOnReturn) {
-        this.testOnReturn = testOnReturn;
-    }
-
-    /**
-     * Get the value for the {@code testWhileIdle} configuration attribute for
+     * Gets the value for the {@code testWhileIdle} configuration attribute for
      * pools created with this configuration instance.
      *
      * @return  The current setting of {@code testWhileIdle} for this
@@ -511,144 +655,144 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
     }
 
     /**
-     * Set the value for the {@code testWhileIdle} configuration attribute for
-     * pools created with this configuration instance.
+     * Gets the value for the {@code timeBetweenEvictionRuns} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @param testWhileIdle The new setting of {@code testWhileIdle}
-     *        for this configuration instance
+     * @return  The current setting of {@code timeBetweenEvictionRuns} for
+     *          this configuration instance
      *
-     * @see GenericObjectPool#getTestWhileIdle()
-     * @see GenericKeyedObjectPool#getTestWhileIdle()
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @since 2.10.0
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
      */
-    public void setTestWhileIdle(final boolean testWhileIdle) {
-        this.testWhileIdle = testWhileIdle;
+    @Deprecated
+    public Duration getTimeBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
     }
 
     /**
-     * Get the value for the {@code timeBetweenEvictionRunsMillis} configuration
+     * Gets the value for the {@code timeBetweenEvictionRuns} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code timeBetweenEvictionRunsMillis} for
+     * @return  The current setting of {@code timeBetweenEvictionRuns} for
      *          this configuration instance
      *
-     * @see GenericObjectPool#getTimeBetweenEvictionRunsMillis()
-     * @see GenericKeyedObjectPool#getTimeBetweenEvictionRunsMillis()
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
      */
+    @Deprecated
     public long getTimeBetweenEvictionRunsMillis() {
-        return timeBetweenEvictionRunsMillis;
+        return durationBetweenEvictionRuns.toMillis();
     }
 
     /**
-     * Set the value for the {@code timeBetweenEvictionRunsMillis} configuration
-     * attribute for pools created with this configuration instance.
+     * Sets the value for the {@code blockWhenExhausted} configuration attribute
+     * for pools created with this configuration instance.
      *
-     * @param timeBetweenEvictionRunsMillis The new setting of
-     *        {@code timeBetweenEvictionRunsMillis} for this configuration
-     *        instance
+     * @param blockWhenExhausted The new setting of {@code blockWhenExhausted}
+     *        for this configuration instance
      *
-     * @see GenericObjectPool#getTimeBetweenEvictionRunsMillis()
-     * @see GenericKeyedObjectPool#getTimeBetweenEvictionRunsMillis()
+     * @see GenericObjectPool#getBlockWhenExhausted()
+     * @see GenericKeyedObjectPool#getBlockWhenExhausted()
      */
-    public void setTimeBetweenEvictionRunsMillis(
-            final long timeBetweenEvictionRunsMillis) {
-        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
+    public void setBlockWhenExhausted(final boolean blockWhenExhausted) {
+        this.blockWhenExhausted = blockWhenExhausted;
     }
 
     /**
-     * Get the value for the {@code evictionPolicyClass} configuration
+     * Sets the value for the {@code evictionPolicyClass} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code evictionPolicyClass} for this
-     *          configuration instance
+     * @param evictionPolicy The new setting of
+     *        {@code evictionPolicyClass} for this configuration instance
      *
      * @see GenericObjectPool#getEvictionPolicy()
      * @see GenericKeyedObjectPool#getEvictionPolicy()
      * @since 2.6.0
      */
-    public EvictionPolicy<T> getEvictionPolicy() {
-        return evictionPolicy;
+    public void setEvictionPolicy(final EvictionPolicy<T> evictionPolicy) {
+        this.evictionPolicy = evictionPolicy;
     }
 
     /**
-     * Get the value for the {@code evictionPolicyClassName} configuration
+     * Sets the value for the {@code evictionPolicyClassName} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code evictionPolicyClassName} for this
-     *          configuration instance
+     * @param evictionPolicyClassName The new setting of
+     *        {@code evictionPolicyClassName} for this configuration instance
      *
      * @see GenericObjectPool#getEvictionPolicyClassName()
      * @see GenericKeyedObjectPool#getEvictionPolicyClassName()
      */
-    public String getEvictionPolicyClassName() {
-        return evictionPolicyClassName;
+    public void setEvictionPolicyClassName(final String evictionPolicyClassName) {
+        this.evictionPolicyClassName = evictionPolicyClassName;
     }
 
     /**
-     * Set the value for the {@code evictionPolicyClass} configuration
+     * Sets the value for the {@code evictorShutdownTimeout} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @param evictionPolicy The new setting of
-     *        {@code evictionPolicyClass} for this configuration instance
+     * @param evictorShutdownTimeoutDuration The new setting of
+     *        {@code evictorShutdownTimeout} for this configuration
+     *        instance
      *
-     * @see GenericObjectPool#getEvictionPolicy()
-     * @see GenericKeyedObjectPool#getEvictionPolicy()
-     * @since 2.6.0
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @since 2.11.0
      */
-    public void setEvictionPolicy(final EvictionPolicy<T> evictionPolicy) {
-        this.evictionPolicy = evictionPolicy;
+    public void setEvictorShutdownTimeout(final Duration evictorShutdownTimeoutDuration) {
+        this.evictorShutdownTimeoutDuration = PoolImplUtils.nonNull(evictorShutdownTimeoutDuration, DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT);
     }
 
     /**
-     * Set the value for the {@code evictionPolicyClassName} configuration
+     * Sets the value for the {@code evictorShutdownTimeout} configuration
      * attribute for pools created with this configuration instance.
      *
-     * @param evictionPolicyClassName The new setting of
-     *        {@code evictionPolicyClassName} for this configuration instance
+     * @param evictorShutdownTimeout The new setting of
+     *        {@code evictorShutdownTimeout} for this configuration
+     *        instance
      *
-     * @see GenericObjectPool#getEvictionPolicyClassName()
-     * @see GenericKeyedObjectPool#getEvictionPolicyClassName()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @since 2.10.0
+     * @deprecated Use {@link #setEvictorShutdownTimeout(Duration)}.
      */
-    public void setEvictionPolicyClassName(final String evictionPolicyClassName) {
-        this.evictionPolicyClassName = evictionPolicyClassName;
+    @Deprecated
+    public void setEvictorShutdownTimeoutMillis(final Duration evictorShutdownTimeout) {
+        setEvictorShutdownTimeout(evictorShutdownTimeout);
     }
 
     /**
-     * Get the value for the {@code blockWhenExhausted} configuration attribute
-     * for pools created with this configuration instance.
+     * Sets the value for the {@code evictorShutdownTimeout} configuration
+     * attribute for pools created with this configuration instance.
      *
-     * @return  The current setting of {@code blockWhenExhausted} for this
-     *          configuration instance
+     * @param evictorShutdownTimeoutMillis The new setting of
+     *        {@code evictorShutdownTimeout} for this configuration
+     *        instance
      *
-     * @see GenericObjectPool#getBlockWhenExhausted()
-     * @see GenericKeyedObjectPool#getBlockWhenExhausted()
+     * @see GenericObjectPool#getEvictorShutdownTimeoutDuration()
+     * @see GenericKeyedObjectPool#getEvictorShutdownTimeoutDuration()
+     * @deprecated Use {@link #setEvictorShutdownTimeout(Duration)}.
      */
-    public boolean getBlockWhenExhausted() {
-        return blockWhenExhausted;
+    @Deprecated
+    public void setEvictorShutdownTimeoutMillis(final long evictorShutdownTimeoutMillis) {
+        setEvictorShutdownTimeout(Duration.ofMillis(evictorShutdownTimeoutMillis));
     }
 
     /**
-     * Set the value for the {@code blockWhenExhausted} configuration attribute
-     * for pools created with this configuration instance.
+     * Sets the value for the {@code fairness} configuration attribute for pools
+     * created with this configuration instance.
      *
-     * @param blockWhenExhausted The new setting of {@code blockWhenExhausted}
+     * @param fairness The new setting of {@code fairness}
      *        for this configuration instance
      *
-     * @see GenericObjectPool#getBlockWhenExhausted()
-     * @see GenericKeyedObjectPool#getBlockWhenExhausted()
-     */
-    public void setBlockWhenExhausted(final boolean blockWhenExhausted) {
-        this.blockWhenExhausted = blockWhenExhausted;
-    }
-
-    /**
-     * Gets the value of the flag that determines if JMX will be enabled for
-     * pools created with this configuration instance.
-     *
-     * @return  The current setting of {@code jmxEnabled} for this configuration
-     *          instance
+     * @see GenericObjectPool#getFairness()
+     * @see GenericKeyedObjectPool#getFairness()
      */
-    public boolean getJmxEnabled() {
-        return jmxEnabled;
+    public void setFairness(final boolean fairness) {
+        this.fairness = fairness;
     }
 
     /**
@@ -663,53 +807,245 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
     }
 
     /**
-     * Gets the value of the JMX name base that will be used as part of the
+     * Sets the value of the JMX name base that will be used as part of the
      * name assigned to JMX enabled pools created with this configuration
      * instance. A value of {@code null} means that the pool will define
      * the JMX name base.
      *
-     * @return  The current setting of {@code jmxNameBase} for this
-     *          configuration instance
+     * @param jmxNameBase The new setting of {@code jmxNameBase}
+     *        for this configuration instance
      */
-    public String getJmxNameBase() {
-        return jmxNameBase;
+    public void setJmxNameBase(final String jmxNameBase) {
+        this.jmxNameBase = jmxNameBase;
     }
 
     /**
-     * Sets the value of the JMX name base that will be used as part of the
+     * Sets the value of the JMX name prefix that will be used as part of the
      * name assigned to JMX enabled pools created with this configuration
-     * instance. A value of {@code null} means that the pool will define
-     * the JMX name base.
+     * instance.
      *
-     * @param jmxNameBase The new setting of {@code jmxNameBase}
+     * @param jmxNamePrefix The new setting of {@code jmxNamePrefix}
      *        for this configuration instance
      */
-    public void setJmxNameBase(final String jmxNameBase) {
-        this.jmxNameBase = jmxNameBase;
+    public void setJmxNamePrefix(final String jmxNamePrefix) {
+        this.jmxNamePrefix = jmxNamePrefix;
     }
 
     /**
-     * Gets the value of the JMX name prefix that will be used as part of the
-     * name assigned to JMX enabled pools created with this configuration
+     * Sets the value for the {@code lifo} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @param lifo The new setting of {@code lifo}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getLifo()
+     * @see GenericKeyedObjectPool#getLifo()
+     */
+    public void setLifo(final boolean lifo) {
+        this.lifo = lifo;
+    }
+
+    /**
+     * Sets the value for the {@code maxWait} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @param maxWaitDuration The new setting of {@code maxWaitDuration}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @since 2.11.0
+     */
+    public void setMaxWait(final Duration maxWaitDuration) {
+        this.maxWaitDuration = PoolImplUtils.nonNull(maxWaitDuration, DEFAULT_MAX_WAIT);
+    }
+
+    /**
+     * Sets the value for the {@code maxWait} configuration attribute for pools
+     * created with this configuration instance.
+     *
+     * @param maxWaitMillis The new setting of {@code maxWaitMillis}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getMaxWaitDuration()
+     * @see GenericKeyedObjectPool#getMaxWaitDuration()
+     * @deprecated Use {@link #setMaxWait(Duration)}.
+     */
+    @Deprecated
+    public void setMaxWaitMillis(final long maxWaitMillis) {
+        setMaxWait(Duration.ofMillis(maxWaitMillis));
+    }
+
+    /**
+     * Sets the value for the {@code minEvictableIdleTime} configuration
+     * attribute for pools created with this configuration instance.
+     *
+     * @param minEvictableIdleTime The new setting of
+     *        {@code minEvictableIdleTime} for this configuration instance
+     *
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @since 2.10.0
+     */
+    public void setMinEvictableIdleTime(final Duration minEvictableIdleTime) {
+        this.minEvictableIdleDuration = PoolImplUtils.nonNull(minEvictableIdleTime, DEFAULT_MIN_EVICTABLE_IDLE_TIME);
+    }
+
+    /**
+     * Sets the value for the {@code minEvictableIdleTime} configuration
+     * attribute for pools created with this configuration instance.
+     *
+     * @param minEvictableIdleTimeMillis The new setting of
+     *        {@code minEvictableIdleTime} for this configuration instance
+     *
+     * @see GenericObjectPool#getMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getMinEvictableIdleDuration()
+     * @deprecated Use {@link #setMinEvictableIdleTime(Duration)}.
+     */
+    @Deprecated
+    public void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
+        this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
+    }
+
+    /**
+     * Sets the value for the {@code numTestsPerEvictionRun} configuration
+     * attribute for pools created with this configuration instance.
+     *
+     * @param numTestsPerEvictionRun The new setting of
+     *        {@code numTestsPerEvictionRun} for this configuration instance
+     *
+     * @see GenericObjectPool#getNumTestsPerEvictionRun()
+     * @see GenericKeyedObjectPool#getNumTestsPerEvictionRun()
+     */
+    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
+        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
+    }
+
+    /**
+     * Sets the value for the {@code softMinEvictableIdleTime}
+     * configuration attribute for pools created with this configuration
      * instance.
      *
-     * @return  The current setting of {@code jmxNamePrefix} for this
-     *          configuration instance
+     * @param softMinEvictableIdleTime The new setting of
+     *        {@code softMinEvictableIdleTime} for this configuration
+     *        instance
+     *
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @since 2.10.0
      */
-    public String getJmxNamePrefix() {
-        return jmxNamePrefix;
+    public void setSoftMinEvictableIdleTime(final Duration softMinEvictableIdleTime) {
+        this.softMinEvictableIdleDuration = PoolImplUtils.nonNull(softMinEvictableIdleTime, DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME);
     }
 
     /**
-     * Sets the value of the JMX name prefix that will be used as part of the
-     * name assigned to JMX enabled pools created with this configuration
+     * Sets the value for the {@code softMinEvictableIdleTime}
+     * configuration attribute for pools created with this configuration
      * instance.
      *
-     * @param jmxNamePrefix The new setting of {@code jmxNamePrefix}
+     * @param softMinEvictableIdleTimeMillis The new setting of
+     *        {@code softMinEvictableIdleTime} for this configuration
+     *        instance
+     *
+     * @see GenericObjectPool#getSoftMinEvictableIdleDuration()
+     * @see GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()
+     * @deprecated Use {@link #setSoftMinEvictableIdleTime(Duration)}.
+     */
+    @Deprecated
+    public void setSoftMinEvictableIdleTimeMillis(
+            final long softMinEvictableIdleTimeMillis) {
+        setSoftMinEvictableIdleTime(Duration.ofMillis(softMinEvictableIdleTimeMillis));
+    }
+
+    /**
+     * Sets the value for the {@code testOnBorrow} configuration attribute for
+     * pools created with this configuration instance.
+     *
+     * @param testOnBorrow The new setting of {@code testOnBorrow}
      *        for this configuration instance
+     *
+     * @see GenericObjectPool#getTestOnBorrow()
+     * @see GenericKeyedObjectPool#getTestOnBorrow()
      */
-    public void setJmxNamePrefix(final String jmxNamePrefix) {
-        this.jmxNamePrefix = jmxNamePrefix;
+    public void setTestOnBorrow(final boolean testOnBorrow) {
+        this.testOnBorrow = testOnBorrow;
+    }
+
+    /**
+     * Sets the value for the {@code testOnCreate} configuration attribute for
+     * pools created with this configuration instance.
+     *
+     * @param testOnCreate The new setting of {@code testOnCreate}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getTestOnCreate()
+     * @see GenericKeyedObjectPool#getTestOnCreate()
+     *
+     * @since 2.2
+     */
+    public void setTestOnCreate(final boolean testOnCreate) {
+        this.testOnCreate = testOnCreate;
+    }
+
+    /**
+     * Sets the value for the {@code testOnReturn} configuration attribute for
+     * pools created with this configuration instance.
+     *
+     * @param testOnReturn The new setting of {@code testOnReturn}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getTestOnReturn()
+     * @see GenericKeyedObjectPool#getTestOnReturn()
+     */
+    public void setTestOnReturn(final boolean testOnReturn) {
+        this.testOnReturn = testOnReturn;
+    }
+
+    /**
+     * Sets the value for the {@code testWhileIdle} configuration attribute for
+     * pools created with this configuration instance.
+     *
+     * @param testWhileIdle The new setting of {@code testWhileIdle}
+     *        for this configuration instance
+     *
+     * @see GenericObjectPool#getTestWhileIdle()
+     * @see GenericKeyedObjectPool#getTestWhileIdle()
+     */
+    public void setTestWhileIdle(final boolean testWhileIdle) {
+        this.testWhileIdle = testWhileIdle;
+    }
+
+    /**
+     * Sets the value for the {@code timeBetweenEvictionRuns} configuration
+     * attribute for pools created with this configuration instance.
+     *
+     * @param timeBetweenEvictionRuns The new setting of
+     *        {@code timeBetweenEvictionRuns} for this configuration
+     *        instance
+     *
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @since 2.10.0
+     */
+    public void setTimeBetweenEvictionRuns(final Duration timeBetweenEvictionRuns) {
+        this.durationBetweenEvictionRuns = PoolImplUtils.nonNull(timeBetweenEvictionRuns, DEFAULT_TIME_BETWEEN_EVICTION_RUNS);
+    }
+
+    /**
+     * Sets the value for the {@code timeBetweenEvictionRuns} configuration
+     * attribute for pools created with this configuration instance.
+     *
+     * @param timeBetweenEvictionRunsMillis The new setting of
+     *        {@code timeBetweenEvictionRuns} for this configuration
+     *        instance
+     *
+     * @see GenericObjectPool#getDurationBetweenEvictionRuns()
+     * @see GenericKeyedObjectPool#getDurationBetweenEvictionRuns()
+     * @deprecated Use {@link #setTimeBetweenEvictionRuns(Duration)}.
+     */
+    @Deprecated
+    public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis));
     }
 
     @Override
@@ -718,12 +1054,12 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
         builder.append(lifo);
         builder.append(", fairness=");
         builder.append(fairness);
-        builder.append(", maxWaitMillis=");
-        builder.append(maxWaitMillis);
-        builder.append(", minEvictableIdleTimeMillis=");
-        builder.append(minEvictableIdleTimeMillis);
-        builder.append(", softMinEvictableIdleTimeMillis=");
-        builder.append(softMinEvictableIdleTimeMillis);
+        builder.append(", maxWaitDuration=");
+        builder.append(maxWaitDuration);
+        builder.append(", minEvictableIdleTime=");
+        builder.append(minEvictableIdleDuration);
+        builder.append(", softMinEvictableIdleTime=");
+        builder.append(softMinEvictableIdleDuration);
         builder.append(", numTestsPerEvictionRun=");
         builder.append(numTestsPerEvictionRun);
         builder.append(", evictionPolicyClassName=");
@@ -736,8 +1072,8 @@ public abstract class BaseObjectPoolConfig<T> extends BaseObject implements Clon
         builder.append(testOnReturn);
         builder.append(", testWhileIdle=");
         builder.append(testWhileIdle);
-        builder.append(", timeBetweenEvictionRunsMillis=");
-        builder.append(timeBetweenEvictionRunsMillis);
+        builder.append(", timeBetweenEvictionRuns=");
+        builder.append(durationBetweenEvictionRuns);
         builder.append(", blockWhenExhausted=");
         builder.append(blockWhenExhausted);
         builder.append(", jmxEnabled=");
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/CallStack.java b/java/org/apache/tomcat/dbcp/pool2/impl/CallStack.java
index fe7aa0d..0f34709 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/CallStack.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/CallStack.java
@@ -18,10 +18,12 @@ package org.apache.tomcat.dbcp.pool2.impl;
 
 import java.io.PrintWriter;
 
+import org.apache.tomcat.dbcp.pool2.PooledObject;
+import org.apache.tomcat.dbcp.pool2.UsageTracking;
+
 /**
  * Strategy for obtaining and printing the current call stack. This is primarily useful for
- * {@link org.apache.tomcat.dbcp.pool2.UsageTracking usage tracking} so
- * that different JVMs and configurations can use more efficient strategies
+ * {@linkplain UsageTracking usage tracking} so that different JVMs and configurations can use more efficient strategies
  * for obtaining the current call stack depending on metadata needs.
  *
  * @see CallStackUtils
@@ -30,13 +32,10 @@ import java.io.PrintWriter;
 public interface CallStack {
 
     /**
-     * Prints the current stack trace if available to a PrintWriter. The format is undefined and is primarily useful
-     * for debugging issues with {@link org.apache.tomcat.dbcp.pool2.PooledObject} usage in user code.
-     *
-     * @param writer a PrintWriter to write the current stack trace to if available
-     * @return true if a stack trace was available to print or false if nothing was printed
+     * Clears the current stack trace snapshot. Subsequent calls to {@link #printStackTrace(PrintWriter)} will be
+     * no-ops until another call to {@link #fillInStackTrace()}.
      */
-    boolean printStackTrace(final PrintWriter writer);
+    void clear();
 
     /**
      * Takes a snapshot of the current call stack. Subsequent calls to {@link #printStackTrace(PrintWriter)} will print
@@ -45,8 +44,11 @@ public interface CallStack {
     void fillInStackTrace();
 
     /**
-     * Clears the current stack trace snapshot. Subsequent calls to {@link #printStackTrace(PrintWriter)} will be
-     * no-ops until another call to {@link #fillInStackTrace()}.
+     * Prints the current stack trace if available to a PrintWriter. The format is undefined and is primarily useful
+     * for debugging issues with {@link PooledObject} usage in user code.
+     *
+     * @param writer a PrintWriter to write the current stack trace to if available
+     * @return true if a stack trace was available to print or false if nothing was printed
      */
-    void clear();
+    boolean printStackTrace(final PrintWriter writer);
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/CallStackUtils.java b/java/org/apache/tomcat/dbcp/pool2/impl/CallStackUtils.java
index a5644ad..831343f 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/CallStackUtils.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/CallStackUtils.java
@@ -26,7 +26,7 @@ import java.security.AccessControlException;
 public final class CallStackUtils {
 
     /**
-     * Returns whether the caller can create a security manager in the current environment.
+     * Tests whether the caller can create a security manager in the current environment.
      *
      * @return {@code true} if it is able to create a security manager in the current environment, {@code false}
      *         otherwise.
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java
index fc618a5..3265fd5 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java
@@ -19,37 +19,36 @@ package org.apache.tomcat.dbcp.pool2.impl;
 import org.apache.tomcat.dbcp.pool2.PooledObject;
 
 /**
- * Provides the default implementation of {@link EvictionPolicy} used by the
- * pools. Objects will be evicted if the following conditions are met:
+ * Provides the default implementation of {@link EvictionPolicy} used by the pools.
+ * <p>
+ * Objects will be evicted if the following conditions are met:
+ * </p>
  * <ul>
- * <li>the object has been idle longer than
- *     {@link GenericObjectPool#getMinEvictableIdleTimeMillis()} /
- *     {@link GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()}</li>
- * <li>there are more than {@link GenericObjectPool#getMinIdle()} /
+ * <li>The object has been idle longer than
+ *     {@link GenericObjectPool#getMinEvictableIdleDuration()} /
+ *     {@link GenericKeyedObjectPool#getMinEvictableIdleDuration()}</li>
+ * <li>There are more than {@link GenericObjectPool#getMinIdle()} /
  *     {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} idle objects in
  *     the pool and the object has been idle for longer than
- *     {@link GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} /
- *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()}
+ *     {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} /
+ *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleDuration()}
  * </ul>
  * <p>
  * This class is immutable and thread-safe.
  * </p>
  *
- * @param <T> the type of objects in the pool
+ * @param <T> the type of objects in the pool.
  *
  * @since 2.0
  */
 public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
 
     @Override
-    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest,
-            final int idleCount) {
-
-        if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
+    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
+        // @formatter:off
+        return (config.getIdleSoftEvictDuration().compareTo(underTest.getIdleDuration()) < 0 &&
                 config.getMinIdle() < idleCount) ||
-                config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
-            return true;
-        }
-        return false;
+                config.getIdleEvictDuration().compareTo(underTest.getIdleDuration()) < 0;
+        // @formatter:on
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObject.java b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObject.java
index 1883041..3894d87 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObject.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObject.java
@@ -17,6 +17,9 @@
 package org.apache.tomcat.dbcp.pool2.impl;
 
 import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Deque;
 
 import org.apache.tomcat.dbcp.pool2.PooledObject;
@@ -38,14 +41,16 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
 
     private final T object;
     private PooledObjectState state = PooledObjectState.IDLE; // @GuardedBy("this") to ensure transitions are valid
-    private final long createTime = System.currentTimeMillis();
-    private volatile long lastBorrowTime = createTime;
-    private volatile long lastUseTime = createTime;
-    private volatile long lastReturnTime = createTime;
-    private volatile boolean logAbandoned = false;
+    private final Clock systemClock = Clock.systemUTC();
+    private final Instant createInstant = now();
+
+    private volatile Instant lastBorrowInstant = createInstant;
+    private volatile Instant lastUseInstant = createInstant;
+    private volatile Instant lastReturnInstant = createInstant;
+    private volatile boolean logAbandoned;
     private volatile CallStack borrowedBy = NoOpCallStack.INSTANCE;
     private volatile CallStack usedBy = NoOpCallStack.INSTANCE;
-    private volatile long borrowedCount = 0;
+    private volatile long borrowedCount;
 
     /**
      * Creates a new instance that wraps the provided object so that the pool can
@@ -57,45 +62,85 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
         this.object = object;
     }
 
+    /**
+     * Allocates the object.
+     *
+     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
+     */
     @Override
-    public T getObject() {
-        return object;
+    public synchronized boolean allocate() {
+        if (state == PooledObjectState.IDLE) {
+            state = PooledObjectState.ALLOCATED;
+            lastBorrowInstant = now();
+            lastUseInstant = lastBorrowInstant;
+            borrowedCount++;
+            if (logAbandoned) {
+                borrowedBy.fillInStackTrace();
+            }
+            return true;
+        }
+        if (state == PooledObjectState.EVICTION) {
+            // TODO Allocate anyway and ignore eviction test
+            state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
+        }
+        // TODO if validating and testOnBorrow == true then pre-allocate for
+        // performance
+        return false;
     }
 
     @Override
-    public long getCreateTime() {
-        return createTime;
+    public int compareTo(final PooledObject<T> other) {
+        final int compareTo = getLastReturnInstant().compareTo(other.getLastReturnInstant());
+        if (compareTo == 0) {
+            // Make sure the natural ordering is broadly consistent with equals
+            // although this will break down if distinct objects have the same
+            // identity hash code.
+            // see java.lang.Comparable Javadocs
+            return System.identityHashCode(this) - System.identityHashCode(other);
+        }
+        return compareTo;
     }
 
+    /**
+     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
+     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}
+     * or {@link PooledObjectState#RETURNING RETURNING}.
+     *
+     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
+     *         or {@link PooledObjectState#RETURNING RETURNING}.
+     */
     @Override
-    public long getActiveTimeMillis() {
-        // Take copies to avoid threading issues
-        final long rTime = lastReturnTime;
-        final long bTime = lastBorrowTime;
-
-        if (rTime > bTime) {
-            return rTime - bTime;
+    public synchronized boolean deallocate() {
+        if (state == PooledObjectState.ALLOCATED || state == PooledObjectState.RETURNING) {
+            state = PooledObjectState.IDLE;
+            lastReturnInstant = now();
+            borrowedBy.clear();
+            return true;
         }
-        return System.currentTimeMillis() - bTime;
-    }
 
-    @Override
-    public long getIdleTimeMillis() {
-        final long elapsed = System.currentTimeMillis() - lastReturnTime;
-        // elapsed may be negative if:
-        // - another thread updates lastReturnTime during the calculation window
-        // - System.currentTimeMillis() is not monotonic (e.g. system time is set back)
-        return elapsed >= 0 ? elapsed : 0;
+        return false;
     }
 
     @Override
-    public long getLastBorrowTime() {
-        return lastBorrowTime;
+    public synchronized boolean endEvictionTest(
+            final Deque<PooledObject<T>> idleQueue) {
+        if (state == PooledObjectState.EVICTION) {
+            state = PooledObjectState.IDLE;
+            return true;
+        }
+        if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD) {
+            state = PooledObjectState.IDLE;
+            if (!idleQueue.offerFirst(this)) {
+                // TODO - Should never happen
+            }
+        }
+
+        return false;
     }
 
     @Override
-    public long getLastReturnTime() {
-        return lastReturnTime;
+    public long getActiveTimeMillis() {
+        return getActiveDuration().toMillis();
     }
 
     /**
@@ -108,148 +153,93 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
         return borrowedCount;
     }
 
-    /**
-     * Returns an estimate of the last time this object was used.  If the class
-     * of the pooled object implements {@link TrackedUse}, what is returned is
-     * the maximum of {@link TrackedUse#getLastUsed()} and
-     * {@link #getLastBorrowTime()}; otherwise this method gives the same
-     * value as {@link #getLastBorrowTime()}.
-     *
-     * @return the last time this object was used
-     */
     @Override
-    public long getLastUsedTime() {
-        if (object instanceof TrackedUse) {
-            return Math.max(((TrackedUse) object).getLastUsed(), lastUseTime);
-        }
-        return lastUseTime;
+    public Instant getCreateInstant() {
+        return createInstant;
     }
 
     @Override
-    public int compareTo(final PooledObject<T> other) {
-        final long lastActiveDiff = this.getLastReturnTime() - other.getLastReturnTime();
-        if (lastActiveDiff == 0) {
-            // Make sure the natural ordering is broadly consistent with equals
-            // although this will break down if distinct objects have the same
-            // identity hash code.
-            // see java.lang.Comparable Javadocs
-            return System.identityHashCode(this) - System.identityHashCode(other);
-        }
-        // handle int overflow
-        return (int)Math.min(Math.max(lastActiveDiff, Integer.MIN_VALUE), Integer.MAX_VALUE);
+    public long getCreateTime() {
+        return createInstant.toEpochMilli();
     }
 
     @Override
-    public String toString() {
-        final StringBuilder result = new StringBuilder();
-        result.append("Object: ");
-        result.append(object.toString());
-        result.append(", State: ");
-        synchronized (this) {
-            result.append(state.toString());
-        }
-        return result.toString();
-        // TODO add other attributes
+    public Duration getIdleDuration() {
+        // elapsed may be negative if:
+        // - another thread updates lastReturnInstant during the calculation window
+        // - System.currentTimeMillis() is not monotonic (e.g. system time is set back)
+        final Duration elapsed = Duration.between(lastReturnInstant, now());
+        return elapsed.isNegative() ? Duration.ZERO : elapsed;
     }
 
     @Override
-    public synchronized boolean startEvictionTest() {
-        if (state == PooledObjectState.IDLE) {
-            state = PooledObjectState.EVICTION;
-            return true;
-        }
+    public Duration getIdleTime() {
+        return getIdleDuration();
+    }
 
-        return false;
+    @Override
+    public long getIdleTimeMillis() {
+        return getIdleDuration().toMillis();
     }
 
     @Override
-    public synchronized boolean endEvictionTest(
-            final Deque<PooledObject<T>> idleQueue) {
-        if (state == PooledObjectState.EVICTION) {
-            state = PooledObjectState.IDLE;
-            return true;
-        } else if (state == PooledObjectState.EVICTION_RETURN_TO_HEAD) {
-            state = PooledObjectState.IDLE;
-            if (!idleQueue.offerFirst(this)) {
-                // TODO - Should never happen
-            }
-        }
+    public Instant getLastBorrowInstant() {
+        return lastBorrowInstant;
+    }
 
-        return false;
+    @Override
+    public long getLastBorrowTime() {
+        return lastBorrowInstant.toEpochMilli();
     }
 
-    /**
-     * Allocates the object.
-     *
-     * @return {@code true} if the original state was {@link PooledObjectState#IDLE IDLE}
-     */
     @Override
-    public synchronized boolean allocate() {
-        if (state == PooledObjectState.IDLE) {
-            state = PooledObjectState.ALLOCATED;
-            lastBorrowTime = System.currentTimeMillis();
-            lastUseTime = lastBorrowTime;
-            borrowedCount++;
-            if (logAbandoned) {
-                borrowedBy.fillInStackTrace();
-            }
-            return true;
-        } else if (state == PooledObjectState.EVICTION) {
-            // TODO Allocate anyway and ignore eviction test
-            state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
-            return false;
-        }
-        // TODO if validating and testOnBorrow == true then pre-allocate for
-        // performance
-        return false;
+    public Instant getLastReturnInstant() {
+        return lastReturnInstant;
+    }
+
+    @Override
+    public long getLastReturnTime() {
+        return lastReturnInstant.toEpochMilli();
     }
 
     /**
-     * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
-     * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}
-     * or {@link PooledObjectState#RETURNING RETURNING}.
+     * Gets an estimate of the last time this object was used.  If the class
+     * of the pooled object implements {@link TrackedUse}, what is returned is
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same
+     * value as {@link #getLastBorrowTime()}.
      *
-     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
-     *         or {@link PooledObjectState#RETURNING RETURNING}.
+     * @return the last Instant this object was used.
      */
     @Override
-    public synchronized boolean deallocate() {
-        if (state == PooledObjectState.ALLOCATED ||
-                state == PooledObjectState.RETURNING) {
-            state = PooledObjectState.IDLE;
-            lastReturnTime = System.currentTimeMillis();
-            borrowedBy.clear();
-            return true;
+    public Instant getLastUsedInstant() {
+        if (object instanceof TrackedUse) {
+            return PoolImplUtils.max(((TrackedUse) object).getLastUsedInstant(), lastUseInstant);
         }
-
-        return false;
+        return lastUseInstant;
     }
 
     /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}
+     * Gets an estimate of the last time this object was used.  If the class
+     * of the pooled object implements {@link TrackedUse}, what is returned is
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
+     * {@link #getLastBorrowTime()}; otherwise this method gives the same
+     * value as {@link #getLastBorrowTime()}.
+     *
+     * @return the last time this object was used
      */
     @Override
-    public synchronized void invalidate() {
-        state = PooledObjectState.INVALID;
-    }
-
-    @Override
-    public void use() {
-        lastUseTime = System.currentTimeMillis();
-        usedBy.fillInStackTrace();
+    public long getLastUsedTime() {
+        return getLastUsedInstant().toEpochMilli();
     }
 
     @Override
-    public void printStackTrace(final PrintWriter writer) {
-        boolean written = borrowedBy.printStackTrace(writer);
-        written |= usedBy.printStackTrace(writer);
-        if (written) {
-            writer.flush();
-        }
+    public T getObject() {
+        return object;
     }
 
     /**
-     * Returns the state of this object.
+     * Gets the state of this object.
      * @return state
      */
     @Override
@@ -258,7 +248,15 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     }
 
     /**
-     * Marks the pooled object as abandoned.
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
+     */
+    @Override
+    public synchronized void invalidate() {
+        state = PooledObjectState.INVALID;
+    }
+
+    /**
+     * Marks the pooled object as {@link PooledObjectState#ABANDONED ABANDONED}.
      */
     @Override
     public synchronized void markAbandoned() {
@@ -266,13 +264,31 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     }
 
     /**
-     * Marks the object as returning to the pool.
+     * Marks the pooled object as {@link PooledObjectState#RETURNING RETURNING}.
      */
     @Override
     public synchronized void markReturning() {
         state = PooledObjectState.RETURNING;
     }
 
+    /**
+     * Gets the current instant of the clock.
+     *
+     * @return the current instant of the clock.
+     */
+    private Instant now() {
+        return systemClock.instant();
+    }
+
+    @Override
+    public void printStackTrace(final PrintWriter writer) {
+        boolean written = borrowedBy.printStackTrace(writer);
+        written |= usedBy.printStackTrace(writer);
+        if (written) {
+            writer.flush();
+        }
+    }
+
     @Override
     public void setLogAbandoned(final boolean logAbandoned) {
         this.logAbandoned = logAbandoned;
@@ -297,4 +313,33 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
             false, requireFullStackTrace);
     }
 
+    @Override
+    public synchronized boolean startEvictionTest() {
+        if (state == PooledObjectState.IDLE) {
+            state = PooledObjectState.EVICTION;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder result = new StringBuilder();
+        result.append("Object: ");
+        result.append(object.toString());
+        result.append(", State: ");
+        synchronized (this) {
+            result.append(state.toString());
+        }
+        return result.toString();
+        // TODO add other attributes
+    }
+
+    @Override
+    public void use() {
+        lastUseInstant = now();
+        usedBy.fillInStackTrace();
+    }
+
+
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfo.java b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfo.java
index 912878b..28f2c10 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfo.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfo.java
@@ -30,10 +30,12 @@ import org.apache.tomcat.dbcp.pool2.PooledObject;
  */
 public class DefaultPooledObjectInfo implements DefaultPooledObjectInfoMBean {
 
+    private static final String PATTERN = "yyyy-MM-dd HH:mm:ss Z";
+
     private final PooledObject<?> pooledObject;
 
     /**
-     * Create a new instance for the given pooled object.
+     * Constructs a new instance for the given pooled object.
      *
      * @param pooledObject The pooled object that this instance will represent
      */
@@ -42,25 +44,29 @@ public class DefaultPooledObjectInfo implements DefaultPooledObjectInfoMBean {
     }
 
     @Override
+    public long getBorrowedCount() {
+        return pooledObject.getBorrowedCount();
+    }
+
+    @Override
     public long getCreateTime() {
-        return pooledObject.getCreateTime();
+        return pooledObject.getCreateInstant().toEpochMilli();
     }
 
     @Override
     public String getCreateTimeFormatted() {
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
-        return sdf.format(Long.valueOf(pooledObject.getCreateTime()));
+        return getTimeMillisFormatted(getCreateTime());
     }
 
     @Override
     public long getLastBorrowTime() {
-        return pooledObject.getLastBorrowTime();
+        return pooledObject.getLastBorrowInstant().toEpochMilli();
     }
 
+
     @Override
     public String getLastBorrowTimeFormatted() {
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
-        return sdf.format(Long.valueOf(pooledObject.getLastBorrowTime()));
+        return getTimeMillisFormatted(getLastBorrowTime());
     }
 
     @Override
@@ -72,18 +78,12 @@ public class DefaultPooledObjectInfo implements DefaultPooledObjectInfoMBean {
 
     @Override
     public long getLastReturnTime() {
-        return pooledObject.getLastReturnTime();
+        return pooledObject.getLastReturnInstant().toEpochMilli();
     }
 
     @Override
     public String getLastReturnTimeFormatted() {
-        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
-        return sdf.format(Long.valueOf(pooledObject.getLastReturnTime()));
-    }
-
-    @Override
-    public String getPooledObjectType() {
-        return pooledObject.getObject().getClass().getName();
+        return getTimeMillisFormatted(getLastReturnTime());
     }
 
     @Override
@@ -92,8 +92,12 @@ public class DefaultPooledObjectInfo implements DefaultPooledObjectInfoMBean {
     }
 
     @Override
-    public long getBorrowedCount() {
-        return pooledObject.getBorrowedCount();
+    public String getPooledObjectType() {
+        return pooledObject.getObject().getClass().getName();
+    }
+
+    private String getTimeMillisFormatted(final long millis) {
+        return new SimpleDateFormat(PATTERN).format(Long.valueOf(millis));
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfoMBean.java b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfoMBean.java
index f4540b6..2d37f4e 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfoMBean.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/DefaultPooledObjectInfoMBean.java
@@ -19,19 +19,28 @@ package org.apache.tomcat.dbcp.pool2.impl;
 /**
  * The interface that defines the information about pooled objects that will be
  * exposed via JMX.
- *
+ * <p>
  * NOTE: This interface exists only to define those attributes and methods that
  *       will be made available via JMX. It must not be implemented by clients
  *       as it is subject to change between major, minor and patch version
  *       releases of commons pool. Clients that implement this interface may
  *       not, therefore, be able to upgrade to a new minor or patch release
  *       without requiring code changes.
+ * </p>
  *
  * @since 2.0
  */
 public interface DefaultPooledObjectInfoMBean {
+
     /**
-     * Obtain the time (using the same basis as
+     * Gets the number of times this object has been borrowed.
+     * @return The number of times this object has been borrowed.
+     * @since 2.1
+     */
+    long getBorrowedCount();
+
+    /**
+     * Gets the time (using the same basis as
      * {@link System#currentTimeMillis()}) that pooled object was created.
      *
      * @return The creation time for the pooled object
@@ -39,7 +48,7 @@ public interface DefaultPooledObjectInfoMBean {
     long getCreateTime();
 
     /**
-     * Obtain the time that pooled object was created.
+     * Gets the time that pooled object was created.
      *
      * @return The creation time for the pooled object formatted as
      *         {@code yyyy-MM-dd HH:mm:ss Z}
@@ -47,7 +56,7 @@ public interface DefaultPooledObjectInfoMBean {
     String getCreateTimeFormatted();
 
     /**
-     * Obtain the time (using the same basis as
+     * Gets the time (using the same basis as
      * {@link System#currentTimeMillis()}) the polled object was last borrowed.
      *
      * @return The time the pooled object was last borrowed
@@ -55,7 +64,7 @@ public interface DefaultPooledObjectInfoMBean {
     long getLastBorrowTime();
 
     /**
-     * Obtain the time that pooled object was last borrowed.
+     * Gets the time that pooled object was last borrowed.
      *
      * @return The last borrowed time for the pooled object formatted as
      *         {@code yyyy-MM-dd HH:mm:ss Z}
@@ -63,16 +72,15 @@ public interface DefaultPooledObjectInfoMBean {
     String getLastBorrowTimeFormatted();
 
     /**
-     * Obtain the stack trace recorded when the pooled object was last borrowed.
+     * Gets the stack trace recorded when the pooled object was last borrowed.
      *
      * @return The stack trace showing which code last borrowed the pooled
      *         object
      */
     String getLastBorrowTrace();
 
-
     /**
-     * Obtain the time (using the same basis as
+     * Gets the time (using the same basis as
      * {@link System#currentTimeMillis()})the wrapped object was last returned.
      *
      * @return The time the object was last returned
@@ -80,7 +88,7 @@ public interface DefaultPooledObjectInfoMBean {
     long getLastReturnTime();
 
     /**
-     * Obtain the time that pooled object was last returned.
+     * Gets the time that pooled object was last returned.
      *
      * @return The last returned time for the pooled object formatted as
      *         {@code yyyy-MM-dd HH:mm:ss Z}
@@ -88,16 +96,7 @@ public interface DefaultPooledObjectInfoMBean {
     String getLastReturnTimeFormatted();
 
     /**
-     * Obtain the name of the class of the pooled object.
-     *
-     * @return The pooled object's class name
-     *
-     * @see Class#getName()
-     */
-    String getPooledObjectType();
-
-    /**
-     * Provides a String form of the wrapper for debug purposes. The format is
+     * Gets a String form of the wrapper for debug purposes. The format is
      * not fixed and may change at any time.
      *
      * @return A string representation of the pooled object
@@ -107,9 +106,11 @@ public interface DefaultPooledObjectInfoMBean {
     String getPooledObjectToString();
 
     /**
-     * Get the number of times this object has been borrowed.
-     * @return The number of times this object has been borrowed.
-     * @since 2.1
+     * Gets the name of the class of the pooled object.
+     *
+     * @return The pooled object's class name
+     *
+     * @see Class#getName()
      */
-    long getBorrowedCount();
+    String getPooledObjectType();
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java b/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java
index b124676..b634518 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java
@@ -16,6 +16,8 @@
  */
 package org.apache.tomcat.dbcp.pool2.impl;
 
+import java.time.Duration;
+
 /**
  * This class is used by pool implementations to pass configuration information
  * to {@link EvictionPolicy} instances. The {@link EvictionPolicy} may also have
@@ -28,68 +30,149 @@ package org.apache.tomcat.dbcp.pool2.impl;
  */
 public class EvictionConfig {
 
-    private final long idleEvictTime;
-    private final long idleSoftEvictTime;
+    private static final Duration MAX_DURATION = Duration.ofMillis(Long.MAX_VALUE);
+    private final Duration idleEvictDuration;
+    private final Duration idleSoftEvictDuration;
     private final int minIdle;
 
     /**
-     * Create a new eviction configuration with the specified parameters.
+     * Creates a new eviction configuration with the specified parameters.
      * Instances are immutable.
      *
-     * @param poolIdleEvictTime Expected to be provided by
-     *        {@link BaseGenericObjectPool#getMinEvictableIdleTimeMillis()}
-     * @param poolIdleSoftEvictTime Expected to be provided by
-     *        {@link BaseGenericObjectPool#getSoftMinEvictableIdleTimeMillis()}
+     * @param idleEvictDuration Expected to be provided by
+     *        {@link BaseGenericObjectPool#getMinEvictableIdleDuration()}
+     * @param idleSoftEvictDuration Expected to be provided by
+     *        {@link BaseGenericObjectPool#getSoftMinEvictableIdleDuration()}
      * @param minIdle Expected to be provided by
      *        {@link GenericObjectPool#getMinIdle()} or
      *        {@link GenericKeyedObjectPool#getMinIdlePerKey()}
+     * @since 2.10.0
      */
-    public EvictionConfig(final long poolIdleEvictTime, final long poolIdleSoftEvictTime,
-            final int minIdle) {
-        if (poolIdleEvictTime > 0) {
-            idleEvictTime = poolIdleEvictTime;
-        } else {
-            idleEvictTime = Long.MAX_VALUE;
-        }
-        if (poolIdleSoftEvictTime > 0) {
-            idleSoftEvictTime = poolIdleSoftEvictTime;
-        } else {
-            idleSoftEvictTime  = Long.MAX_VALUE;
-        }
+    public EvictionConfig(final Duration idleEvictDuration, final Duration idleSoftEvictDuration, final int minIdle) {
+        this.idleEvictDuration = PoolImplUtils.isPositive(idleEvictDuration) ? idleEvictDuration : MAX_DURATION;
+        this.idleSoftEvictDuration = PoolImplUtils.isPositive(idleSoftEvictDuration) ? idleSoftEvictDuration : MAX_DURATION;
         this.minIdle = minIdle;
     }
 
     /**
-     * Obtain the {@code idleEvictTime} for this eviction configuration
+     * Creates a new eviction configuration with the specified parameters.
+     * Instances are immutable.
+     *
+     * @param poolIdleEvictMillis Expected to be provided by
+     *        {@link BaseGenericObjectPool#getMinEvictableIdleDuration()}
+     * @param poolIdleSoftEvictMillis Expected to be provided by
+     *        {@link BaseGenericObjectPool#getSoftMinEvictableIdleDuration()}
+     * @param minIdle Expected to be provided by
+     *        {@link GenericObjectPool#getMinIdle()} or
+     *        {@link GenericKeyedObjectPool#getMinIdlePerKey()}
+     * @deprecated Use {@link #EvictionConfig(Duration, Duration, int)}.
+     */
+    @Deprecated
+    public EvictionConfig(final long poolIdleEvictMillis, final long poolIdleSoftEvictMillis, final int minIdle) {
+        this(Duration.ofMillis(poolIdleEvictMillis), Duration.ofMillis(poolIdleSoftEvictMillis), minIdle);
+    }
+
+    /**
+     * Gets the {@code idleEvictTime} for this eviction configuration
+     * instance.
+     * <p>
+     * How the evictor behaves based on this value will be determined by the
+     * configured {@link EvictionPolicy}.
+     * </p>
+     *
+     * @return The {@code idleEvictTime}.
+     * @since 2.11.0
+     */
+    public Duration getIdleEvictDuration() {
+        return idleEvictDuration;
+    }
+
+    /**
+     * Gets the {@code idleEvictTime} for this eviction configuration
      * instance.
      * <p>
      * How the evictor behaves based on this value will be determined by the
      * configured {@link EvictionPolicy}.
+     * </p>
      *
      * @return The {@code idleEvictTime} in milliseconds
+     * @deprecated Use {@link #getIdleEvictDuration()}.
      */
+    @Deprecated
     public long getIdleEvictTime() {
-        return idleEvictTime;
+        return idleEvictDuration.toMillis();
+    }
+
+    /**
+     * Gets the {@code idleEvictTime} for this eviction configuration
+     * instance.
+     * <p>
+     * How the evictor behaves based on this value will be determined by the
+     * configured {@link EvictionPolicy}.
+     * </p>
+     *
+     * @return The {@code idleEvictTime}.
+     * @since 2.10.0
+     * @deprecated Use {@link #getIdleEvictDuration()}.
+     */
+    @Deprecated
+    public Duration getIdleEvictTimeDuration() {
+        return idleEvictDuration;
+    }
+
+    /**
+     * Gets the {@code idleSoftEvictTime} for this eviction configuration
+     * instance.
+     * <p>
+     * How the evictor behaves based on this value will be determined by the
+     * configured {@link EvictionPolicy}.
+     * </p>
+     *
+     * @return The (@code idleSoftEvictTime} in milliseconds
+     * @since 2.11.0
+     */
+    public Duration getIdleSoftEvictDuration() {
+        return idleSoftEvictDuration;
     }
 
     /**
-     * Obtain the {@code idleSoftEvictTime} for this eviction configuration
+     * Gets the {@code idleSoftEvictTime} for this eviction configuration
      * instance.
      * <p>
      * How the evictor behaves based on this value will be determined by the
      * configured {@link EvictionPolicy}.
+     * </p>
      *
      * @return The (@code idleSoftEvictTime} in milliseconds
+     * @deprecated Use {@link #getIdleSoftEvictDuration()}.
      */
+    @Deprecated
     public long getIdleSoftEvictTime() {
-        return idleSoftEvictTime;
+        return idleSoftEvictDuration.toMillis();
+    }
+
+    /**
+     * Gets the {@code idleSoftEvictTime} for this eviction configuration
+     * instance.
+     * <p>
+     * How the evictor behaves based on this value will be determined by the
+     * configured {@link EvictionPolicy}.
+     * </p>
+     *
+     * @return The (@code idleSoftEvictTime} in milliseconds
+     * @deprecated Use {@link #getIdleSoftEvictDuration()}.
+     */
+    @Deprecated
+    public Duration getIdleSoftEvictTimeDuration() {
+        return idleSoftEvictDuration;
     }
 
     /**
-     * Obtain the {@code minIdle} for this eviction configuration instance.
+     * Gets the {@code minIdle} for this eviction configuration instance.
      * <p>
      * How the evictor behaves based on this value will be determined by the
      * configured {@link EvictionPolicy}.
+     * </p>
      *
      * @return The {@code minIdle}
      */
@@ -103,10 +186,10 @@ public class EvictionConfig {
     @Override
     public String toString() {
         final StringBuilder builder = new StringBuilder();
-        builder.append("EvictionConfig [idleEvictTime=");
-        builder.append(idleEvictTime);
-        builder.append(", idleSoftEvictTime=");
-        builder.append(idleSoftEvictTime);
+        builder.append("EvictionConfig [idleEvictDuration=");
+        builder.append(idleEvictDuration);
+        builder.append(", idleSoftEvictDuration=");
+        builder.append(idleSoftEvictDuration);
         builder.append(", minIdle=");
         builder.append(minIdle);
         builder.append("]");
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java b/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java
index 0e5759c..8e86e1a 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java
@@ -19,6 +19,7 @@ package org.apache.tomcat.dbcp.pool2.impl;
 import java.lang.ref.WeakReference;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map.Entry;
 import java.util.concurrent.ScheduledFuture;
@@ -47,108 +48,6 @@ import java.util.concurrent.TimeUnit;
  */
 class EvictionTimer {
 
-    /** Executor instance */
-    private static ScheduledThreadPoolExecutor executor; //@GuardedBy("EvictionTimer.class")
-
-    /** Keys are weak references to tasks, values are runners managed by executor. */
-    private static final HashMap<WeakReference<Runnable>, WeakRunner> taskMap = new HashMap<>(); // @GuardedBy("EvictionTimer.class")
-
-    /** Prevents instantiation */
-    private EvictionTimer() {
-        // Hide the default constructor
-    }
-
-
-    /**
-     * @since 2.4.3
-     */
-    @Override
-    public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append("EvictionTimer []");
-        return builder.toString();
-    }
-
-    /**
-     * Adds the specified eviction task to the timer. Tasks that are added with
-     * a call to this method *must* call {@link
-     * #cancel(org.apache.tomcat.dbcp.pool2.impl.BaseGenericObjectPool.Evictor, long, TimeUnit, boolean)}
-     * to cancel the task to prevent memory and/or thread leaks in application
-     * server environments.
-     *
-     * @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(
-            final BaseGenericObjectPool<?>.Evictor task, final long delay, final long period) {
-        if (null == executor) {
-            executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
-            executor.setRemoveOnCancelPolicy(true);
-            executor.scheduleAtFixedRate(new Reaper(), delay, period, TimeUnit.MILLISECONDS);
-        }
-        final WeakReference<Runnable> ref = new WeakReference<>(task);
-        final WeakRunner runner = new WeakRunner(ref);
-        final ScheduledFuture<?> scheduledFuture =
-                executor.scheduleWithFixedDelay(runner, delay, period, TimeUnit.MILLISECONDS);
-        task.setScheduledFuture(scheduledFuture);
-        taskMap.put(ref, runner);
-    }
-
-    /**
-     * Removes the specified eviction task from the timer.
-     *
-     * @param evictor   Task to be cancelled.
-     * @param timeout   If the associated executor is no longer required, how
-     *                  long should this thread wait for the executor to
-     *                  terminate?
-     * @param unit      The units for the specified timeout.
-     * @param restarting The state of the evictor.
-     */
-    static synchronized void cancel(
-            final BaseGenericObjectPool<?>.Evictor evictor, final long timeout, final TimeUnit unit, final boolean restarting) {
-        if (evictor != null) {
-            evictor.cancel();
-            remove(evictor);
-        }
-        if (!restarting && executor != null) {
-            if (taskMap.isEmpty()) {
-                executor.shutdown();
-                try {
-                    executor.awaitTermination(timeout, unit);
-                } catch (final InterruptedException e) {
-                    // Swallow
-                    // Significant API changes would be required to propagate this
-                }
-                executor.setCorePoolSize(0);
-                executor = null;
-            }
-        }
-    }
-
-    /**
-     * Removes evictor from the task set and executor.
-     * Only called when holding the class lock.
-     *
-     * @param evictor Eviction task to remove
-     */
-    private static void remove(final BaseGenericObjectPool<?>.Evictor evictor) {
-        for (Entry<WeakReference<Runnable>, WeakRunner> entry : taskMap.entrySet()) {
-            if (entry.getKey().get() == evictor) {
-                executor.remove(entry.getValue());
-                taskMap.remove(entry.getKey());
-                break;
-            }
-        }
-    }
-
-    /**
-     * @return the number of eviction tasks under management.
-     */
-    static synchronized int getNumTasks() {
-        return taskMap.size();
-    }
-
     /**
      * Thread factory that creates a daemon thread, with the context class loader from this class.
      */
@@ -156,7 +55,7 @@ class EvictionTimer {
 
         @Override
         public Thread newThread(final Runnable runnable) {
-            final Thread thread = new Thread(null, runnable, "commons-pool-evictor-thread");
+            final Thread thread = new Thread(null, runnable, "commons-pool-evictor");
             thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook().
             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                 thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());
@@ -175,7 +74,7 @@ class EvictionTimer {
         @Override
         public void run() {
             synchronized (EvictionTimer.class) {
-                for (Entry<WeakReference<Runnable>, WeakRunner> entry : taskMap.entrySet()) {
+                for (final Entry<WeakReference<Runnable>, WeakRunner> entry : taskMap.entrySet()) {
                     if (entry.getKey().get() == null) {
                         executor.remove(entry.getValue());
                         taskMap.remove(entry.getKey());
@@ -218,4 +117,103 @@ class EvictionTimer {
             }
         }
     }
+
+
+    /** Executor instance */
+    private static ScheduledThreadPoolExecutor executor; //@GuardedBy("EvictionTimer.class")
+
+    /** Keys are weak references to tasks, values are runners managed by executor. */
+    private static final HashMap<WeakReference<Runnable>, WeakRunner> taskMap = new HashMap<>(); // @GuardedBy("EvictionTimer.class")
+
+    /**
+     * Removes the specified eviction task from the timer.
+     *
+     * @param evictor   Task to be cancelled.
+     * @param timeout   If the associated executor is no longer required, how
+     *                  long should this thread wait for the executor to
+     *                  terminate?
+     * @param restarting The state of the evictor.
+     */
+    static synchronized void cancel(final BaseGenericObjectPool<?>.Evictor evictor, final Duration timeout,
+            final boolean restarting) {
+        if (evictor != null) {
+            evictor.cancel();
+            remove(evictor);
+        }
+        if (!restarting && executor != null && taskMap.isEmpty()) {
+            executor.shutdown();
+            try {
+                executor.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS);
+            } catch (final InterruptedException e) {
+                // Swallow
+                // Significant API changes would be required to propagate this
+            }
+            executor.setCorePoolSize(0);
+            executor = null;
+        }
+    }
+
+    /**
+     * @return the number of eviction tasks under management.
+     */
+    static synchronized int getNumTasks() {
+        return taskMap.size();
+    }
+
+    /**
+     * Removes evictor from the task set and executor.
+     * Only called when holding the class lock.
+     *
+     * @param evictor Eviction task to remove
+     */
+    private static void remove(final BaseGenericObjectPool<?>.Evictor evictor) {
+        for (final Entry<WeakReference<Runnable>, WeakRunner> entry : taskMap.entrySet()) {
+            if (entry.getKey().get() == evictor) {
+                executor.remove(entry.getValue());
+                taskMap.remove(entry.getKey());
+                break;
+            }
+        }
+    }
+
+    /**
+     * Adds the specified eviction task to the timer. Tasks that are added with
+     * a call to this method *must* call {@link
+     * #cancel(BaseGenericObjectPool.Evictor, Duration, boolean)}
+     * to cancel the task to prevent memory and/or thread leaks in application
+     * server environments.
+     *
+     * @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(
+            final BaseGenericObjectPool<?>.Evictor task, final Duration delay, final Duration period) {
+        if (null == executor) {
+            executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
+            executor.setRemoveOnCancelPolicy(true);
+            executor.scheduleAtFixedRate(new Reaper(), delay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
+        }
+        final WeakReference<Runnable> ref = new WeakReference<>(task);
+        final WeakRunner runner = new WeakRunner(ref);
+        final ScheduledFuture<?> scheduledFuture = executor.scheduleWithFixedDelay(runner, delay.toMillis(),
+                period.toMillis(), TimeUnit.MILLISECONDS);
+        task.setScheduledFuture(scheduledFuture);
+        taskMap.put(ref, runner);
+    }
+
+    /** Prevents instantiation */
+    private EvictionTimer() {
+        // Hide the default constructor
+    }
+
+    /**
+     * @since 2.4.3
+     */
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("EvictionTimer []");
+        return builder.toString();
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java b/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java
index ebe4027..0ac5dac 100644
--- a/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java
+++ b/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tomcat.dbcp.pool2.impl;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.HashMap;
@@ -24,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
@@ -32,6 +34,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.stream.Collectors;
 
 import org.apache.tomcat.dbcp.pool2.DestroyMode;
 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
@@ -39,6 +42,8 @@ import org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory;
 import org.apache.tomcat.dbcp.pool2.PoolUtils;
 import org.apache.tomcat.dbcp.pool2.PooledObject;
 import org.apache.tomcat.dbcp.pool2.PooledObjectState;
+import org.apache.tomcat.dbcp.pool2.SwallowedExceptionListener;
+import org.apache.tomcat.dbcp.pool2.UsageTracking;
 
 /**
  * A configurable {@code KeyedObjectPool} implementation.
@@ -83,19 +88,167 @@ import org.apache.tomcat.dbcp.pool2.PooledObjectState;
  * @since 2.0
  */
 public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
-        implements KeyedObjectPool<K, T>, GenericKeyedObjectPoolMXBean<K> {
+        implements KeyedObjectPool<K, T>, GenericKeyedObjectPoolMXBean<K>, UsageTracking<T> {
 
     /**
-     * Create a new {@code GenericKeyedObjectPool} using defaults from
+     * Maintains information on the per key queue for a given key.
+     *
+     * @param <S> type of objects in the pool
+     */
+    private class ObjectDeque<S> {
+
+        private final LinkedBlockingDeque<PooledObject<S>> idleObjects;
+
+        /*
+         * Number of instances created - number destroyed.
+         * Invariant: createCount <= maxTotalPerKey
+         */
+        private final AtomicInteger createCount = new AtomicInteger(0);
+
+        private long makeObjectCount;
+        private final Object makeObjectCountLock = new Object();
+
+        /*
+         * The map is keyed on pooled instances, wrapped to ensure that
+         * they work properly as keys.
+         */
+        private final Map<IdentityWrapper<S>, PooledObject<S>> allObjects =
+                new ConcurrentHashMap<>();
+
+        /*
+         * Number of threads with registered interest in this key.
+         * register(K) increments this counter and deRegister(K) decrements it.
+         * Invariant: empty keyed pool will not be dropped unless numInterested
+         *            is 0.
+         */
+        private final AtomicLong numInterested = new AtomicLong();
+
+        /**
+         * Constructs a new ObjecDeque with the given fairness policy.
+         * @param fairness true means client threads waiting to borrow / return instances
+         * will be served as if waiting in a FIFO queue.
+         */
+        public ObjectDeque(final boolean fairness) {
+            idleObjects = new LinkedBlockingDeque<>(fairness);
+        }
+
+        /**
+         * Gets all the objects for the current key.
+         *
+         * @return All the objects
+         */
+        public Map<IdentityWrapper<S>, PooledObject<S>> getAllObjects() {
+            return allObjects;
+        }
+
+        /**
+         * Gets the count of the number of objects created for the current
+         * key.
+         *
+         * @return The number of objects created for this key
+         */
+        public AtomicInteger getCreateCount() {
+            return createCount;
+        }
+
+        /**
+         * Gets the idle objects for the current key.
+         *
+         * @return The idle objects
+         */
+        public LinkedBlockingDeque<PooledObject<S>> getIdleObjects() {
+            return idleObjects;
+        }
+
+        /**
+         * Gets the number of threads with an interest registered in this key.
+         *
+         * @return The number of threads with a registered interest in this key
+         */
+        public AtomicLong getNumInterested() {
+            return numInterested;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder builder = new StringBuilder();
+            builder.append("ObjectDeque [idleObjects=");
+            builder.append(idleObjects);
+            builder.append(", createCount=");
+            builder.append(createCount);
+            builder.append(", allObjects=");
+            builder.append(allObjects);
+            builder.append(", numInterested=");
+            builder.append(numInterested);
+            builder.append("]");
+            return builder.toString();
+        }
+
+    }
+
+    private static final Integer ZERO = Integer.valueOf(0);
+
+    // JMX specific attributes
+    private static final String ONAME_BASE =
+            "org.apache.commons.pool2:type=GenericKeyedObjectPool,name=";
+
+    //--- configuration attributes ---------------------------------------------
+    private volatile int maxIdlePerKey =
+            GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY;
+
+    private volatile int minIdlePerKey =
+            GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY;
+
+
+    private volatile int maxTotalPerKey =
+            GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
+
+    private final KeyedPooledObjectFactory<K, T> factory;
+
+    private final boolean fairness;
+
+    /*
+     * My hash of sub-pools (ObjectQueue). The list of keys <b>must</b> be kept
+     * in step with {@link #poolKeyList} using {@link #keyLock} to ensure any
+     * changes to the list of current keys is made in a thread-safe manner.
+     */
+    private final Map<K, ObjectDeque<T>> poolMap =
+            new ConcurrentHashMap<>(); // @GuardedBy("keyLock") for write access (and some read access)
+
+    /*
+     * List of pool keys - used to control eviction order. The list of keys
+     * <b>must</b> be kept in step with {@link #poolMap} using {@link #keyLock}
+     * to ensure any changes to the list of current keys is made in a
+     * thread-safe manner.
+     */
+    private final List<K> poolKeyList = new ArrayList<>(); // @GuardedBy("keyLock")
+
+    private final ReadWriteLock keyLock = new ReentrantReadWriteLock(true);
+
+    /*
+     * The combined count of the currently active objects for all keys and those
+     * in the process of being created. Under load, it may exceed
+     * {@link #maxTotal} but there will never be more than {@link #maxTotal}
+     * created at any one time.
+     */
+    private final AtomicInteger numTotal = new AtomicInteger(0);
+
+    private Iterator<K> evictionKeyIterator; // @GuardedBy("evictionLock")
+
+
+    private K evictionKey; // @GuardedBy("evictionLock")
+
+    /**
+     * Constructs a new {@code GenericKeyedObjectPool} using defaults from
      * {@link GenericKeyedObjectPoolConfig}.
      * @param factory the factory to be used to create entries
      */
     public GenericKeyedObjectPool(final KeyedPooledObjectFactory<K, T> factory) {
-        this(factory, new GenericKeyedObjectPoolConfig<T>());
+        this(factory, new GenericKeyedObjectPoolConfig<>());
     }
 
     /**
-     * Create a new {@code GenericKeyedObjectPool} using a specific
+     * Constructs a new {@code GenericKeyedObjectPool} using a specific
      * configuration.
      *
      * @param factory the factory to be used to create entries
@@ -111,7 +264,7 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
 
         if (factory == null) {
             jmxUnregister(); // tidy up
-            throw new IllegalArgumentException("factory may not be null");
+            throw new IllegalArgumentException("Factory may not be null");
         }
         this.factory = factory;
         this.fairness = config.getFairness();
@@ -120,156 +273,84 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
     }
 
     /**
-     * Returns the limit on the number of object instances allocated by the pool
-     * (checked out or idle), per key. When the limit is reached, the sub-pool
-     * is said to be exhausted. A negative value indicates no limit.
-     *
-     * @return the limit on the number of active instances per key
+     * Creates a new {@code GenericKeyedObjectPool} that tracks and destroys
+     * objects that are checked out, but never returned to the pool.
      *
-     * @see #setMaxTotalPerKey
+     * @param factory   The object factory to be used to create object instances
+     *                  used by this pool
+     * @param config    The base pool 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.
+     * @param abandonedConfig  Configuration for abandoned object identification
+     *                         and removal.  The configuration is used by value.
+     * @since 2.10.0
      */
-    @Override
-    public int getMaxTotalPerKey() {
-        return maxTotalPerKey;
+    public GenericKeyedObjectPool(final KeyedPooledObjectFactory<K, T> factory,
+            final GenericKeyedObjectPoolConfig<T> config, final AbandonedConfig abandonedConfig) {
... 7611 lines suppressed ...

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org