You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2021/06/03 13:15:41 UTC

[commons-pool] branch master updated: Track timestamps with Instants instead of longs.

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

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-pool.git


The following commit(s) were added to refs/heads/master by this push:
     new 956878c  Track timestamps with Instants instead of longs.
956878c is described below

commit 956878cdad991254def19da0ed7b21340599d8c6
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Jun 3 09:15:37 2021 -0400

    Track timestamps with Instants instead of longs.
    
    There is currently no increased precision on Java 8, but starting with
    Java 9, the JRE SystemClock precision is increased usually down to
    microseconds, or tenth of microseconds, depending on the OS, Hardware,
    and JVM implementation.
    
    Add and use:
    - DefaultPooledObject.getCreateInstant()
    - DefaultPooledObject.getLastUsedInstant()
    - PooledObject.getCreateInstant()
    - PooledObject.getLastBorrowInstant()
    - PooledObject.getLastReturnInstant()
    - PooledObject.getLastUsedInstant()
    - TrackedUse#getLastUsedInstant()
---
 pom.xml                                            |   6 +-
 src/changes/changes.xml                            |  14 ++-
 .../org/apache/commons/pool2/PooledObject.java     | 108 ++++++++++++++++-----
 .../java/org/apache/commons/pool2/TrackedUse.java  |  26 ++++-
 .../apache/commons/pool2/impl/AbandonedConfig.java |   4 +-
 .../commons/pool2/impl/DefaultEvictionPolicy.java  |  13 ++-
 .../commons/pool2/impl/DefaultPooledObject.java    |  93 +++++++++++-------
 .../commons/pool2/impl/GenericKeyedObjectPool.java |  10 +-
 .../commons/pool2/impl/GenericObjectPool.java      |  13 +--
 .../apache/commons/pool2/impl/PoolImplUtils.java   |  26 +++++
 .../pool2/impl/TestAbandonedObjectPool.java        |  14 +++
 .../commons/pool2/impl/TestPoolImplUtils.java      |  34 +++++--
 12 files changed, 263 insertions(+), 98 deletions(-)

diff --git a/pom.xml b/pom.xml
index 2363ba4..43b6d47 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
   </parent>
 
   <artifactId>commons-pool2</artifactId>
-  <version>2.10.1-SNAPSHOT</version>
+  <version>2.11.0-SNAPSHOT</version>
   <name>Apache Commons Pool</name>
 
   <inceptionYear>2001</inceptionYear>
@@ -158,7 +158,7 @@
     <commons.module.name>org.apache.commons.pool2</commons.module.name>
     <commons.rc.version>RC1</commons.rc.version>
     <!-- Java 8 -->
-    <commons.release.version>2.10.0</commons.release.version>
+    <commons.release.version>2.11.0</commons.release.version>
     <commons.release.desc>(Java 8)</commons.release.desc>
     <!-- Java 7 -->
     <commons.release.2.version>2.6.2</commons.release.2.version>
@@ -185,7 +185,7 @@
     <spotbugs.impl.version>4.2.3</spotbugs.impl.version>
 
     <!-- Commons Release Plugin -->
-    <commons.bc.version>2.9.0</commons.bc.version>
+    <commons.bc.version>2.10.0</commons.bc.version>
     <commons.release.isDistModule>true</commons.release.isDistModule>
     <commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>    
     <commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 342d2b8..7476d58 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -43,7 +43,19 @@ The <action> type attribute can be add,update,fix,remove.
     <title>Apache Commons Pool Release Notes</title>
   </properties>
   <body>
-  <release version="2.10.1" date="2021-MM-DD" description="This is a RRR release (Java 8).">
+  <release version="2.11.0" date="2021-MM-DD" description="This is a feature release (Java 8).">
+    <action dev="ggregory" type="add" due-to="Gary Gregory">
+      Track timestamps with Instants instead of longs. 
+      There is currently no increased precision on Java 8, but starting with Java 9, the JRE SystemClock precision is increased usually down to microseconds, or tenth of microseconds, depending on the OS, Hardware, and JVM implementation.
+      Add and use: 
+      - DefaultPooledObject.getCreateInstant()
+      - DefaultPooledObject.getLastUsedInstant()
+      - PooledObject.getCreateInstant()
+      - PooledObject.getLastBorrowInstant()
+      - PooledObject.getLastReturnInstant()
+      - PooledObject.getLastUsedInstant()
+      - TrackedUse#getLastUsedInstant()
+    </action>
   </release>
   <release version="2.10.0" date="2021-05-28" description="This is a feature release (Java 8).">
     <!-- ADD -->
diff --git a/src/main/java/org/apache/commons/pool2/PooledObject.java b/src/main/java/org/apache/commons/pool2/PooledObject.java
index 2e46997..cebcf2b 100644
--- a/src/main/java/org/apache/commons/pool2/PooledObject.java
+++ b/src/main/java/org/apache/commons/pool2/PooledObject.java
@@ -18,6 +18,7 @@ package org.apache.commons.pool2;
 
 import java.io.PrintWriter;
 import java.time.Duration;
+import java.time.Instant;
 import java.util.Deque;
 
 /**
@@ -58,7 +59,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * Deallocates the object and sets it {@link PooledObjectState#IDLE IDLE}
      * if it is currently {@link PooledObjectState#ALLOCATED ALLOCATED}.
      *
-     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}
+     * @return {@code true} if the state was {@link PooledObjectState#ALLOCATED ALLOCATED}.
      */
     boolean deallocate();
 
@@ -66,9 +67,9 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * Called to inform the object that the eviction test has ended.
      *
      * @param idleQueue The queue of idle objects to which the object should be
-     *                  returned
+     *                  returned.
      *
-     * @return  Currently not used
+     * @return  Currently not used.
      */
     boolean endEvictionTest(Deque<PooledObject<T>> idleQueue);
 
@@ -76,15 +77,20 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     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).
+     * 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.12.0
+     * @return The duration last spent in the active state.
+     * @since 2.10.0
      */
     default Duration getActiveTime() {
-        return Duration.ofMillis(getActiveTimeMillis());
+        final Instant lastReturnInstant = getLastReturnInstant();
+        final Instant lastBorrowInstant = getLastBorrowInstant();
+        // @formatter:off
+        return lastReturnInstant.isAfter(lastBorrowInstant) ?
+                Duration.between(lastBorrowInstant, lastReturnInstant) :
+                Duration.between(Instant.now(), lastBorrowInstant);
+        // @formatter:on
     }
 
     /**
@@ -92,14 +98,16 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * 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() {
@@ -107,11 +115,23 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     }
 
     /**
+     * Gets the time (using the same basis as {@link Instant#now()}) that this object was created.
+     *
+     * @return The creation time for the wrapped object.
+     * @since 2.11.0
+     */
+    default Instant getCreateInstant() {
+        return Instant.ofEpochMilli(getCreateTime());
+    }
+
+    /**
      * Gets the time (using the same basis as
      * {@link System#currentTimeMillis()}) that this object was created.
      *
-     * @return The creation time for the wrapped object
+     * @return The creation time for the wrapped object.
+     * @deprecated Use {@link #getCreateInstant()} which offers the best precision.
      */
+    @Deprecated
     long getCreateTime();
 
     /**
@@ -131,40 +151,80 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * 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
+     * @return The time in milliseconds last spent in the idle state.
+     * @deprecated Use {@link #getIdleTime()} which offers the best precision.
      */
+    @Deprecated
     long getIdleTimeMillis();
 
     /**
      * Gets the time the wrapped object was last borrowed.
      *
-     * @return The time the object was last borrowed
+     * @return The time the object was last borrowed.
+     * @since 2.11.0
+     */
+    default Instant getLastBorrowInstant() {
+        return Instant.ofEpochMilli(getLastBorrowTime());
+    }
+
+    /**
+     * 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.
      */
+    @Deprecated
     long getLastBorrowTime();
 
     /**
+     * Gets the time the wrapped object was last borrowed.
+     *
+     * @return The time the object was last borrowed.
+     * @since 2.11.0
+     */
+    default Instant getLastReturnInstant() {
+        return Instant.ofEpochMilli(getLastReturnTime());
+    }
+
+    /**
      * Gets the time the wrapped object was last returned.
      *
-     * @return The time the object was last 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 the last time this object was used
+     * @since 2.11.0
+     */
+    default Instant getLastUsedInstant() {
+        return Instant.ofEpochMilli(getLastUsedTime());
+    }
+
+    /**
      * 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#getLastUsed()} and
+     * 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
+     * @return the last time this object was used.
+     * @deprecated Use {@link #getLastUsedInstant()} which offers the best precision.
      */
+    @Deprecated
     long getLastUsedTime();
 
     /**
      * Gets the underlying object that is wrapped by this instance of
      * {@link PooledObject}.
      *
-     * @return The wrapped object
+     * @return The wrapped object.
      */
     T getObject();
 
@@ -178,7 +238,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     int hashCode();
 
     /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
      */
     void invalidate();
 
@@ -197,7 +257,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * 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
+     * @param   writer  The destination for the debug output.
      */
     void printStackTrace(PrintWriter writer);
 
@@ -207,7 +267,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * borrow this object.
      *
      * @param   logAbandoned    The new configuration setting for abandoned
-     *                          object tracking
+     *                          object tracking.
      */
     void setLogAbandoned(boolean logAbandoned);
 
@@ -216,7 +276,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      * 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) {
@@ -229,7 +289,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
      *
      * @return {@code true} if the object was placed in the
      *         {@link PooledObjectState#EVICTION} state otherwise
-     *         {@code false}
+     *         {@code false}.
      */
     boolean startEvictionTest();
 
@@ -243,7 +303,7 @@ public interface PooledObject<T> extends Comparable<PooledObject<T>> {
     String toString();
 
     /**
-     * Record the current stack trace as the last time the object was used.
+     * Records the current stack trace as the last time the object was used.
      */
     void use();
 
diff --git a/src/main/java/org/apache/commons/pool2/TrackedUse.java b/src/main/java/org/apache/commons/pool2/TrackedUse.java
index d4dfbbb..171d9d0 100644
--- a/src/main/java/org/apache/commons/pool2/TrackedUse.java
+++ b/src/main/java/org/apache/commons/pool2/TrackedUse.java
@@ -16,12 +16,12 @@
  */
 package org.apache.commons.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
  */
@@ -31,6 +31,22 @@ public interface TrackedUse {
      * Gets the last time this object was used in milliseconds.
      *
      * @return long time 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 time as an Instant.
+     * @since 2.11.0
+     */
+    default Instant getLastUsedInstant() {
+        return Instant.ofEpochSecond(getLastUsed());
+    }
 }
diff --git a/src/main/java/org/apache/commons/pool2/impl/AbandonedConfig.java b/src/main/java/org/apache/commons/pool2/impl/AbandonedConfig.java
index 08a79e7..658a05b 100644
--- a/src/main/java/org/apache/commons/pool2/impl/AbandonedConfig.java
+++ b/src/main/java/org/apache/commons/pool2/impl/AbandonedConfig.java
@@ -144,7 +144,7 @@ public class AbandonedConfig {
      * <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#getLastUsed()} (if this class of the object implements
+     * {@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>
@@ -161,7 +161,7 @@ public class AbandonedConfig {
      * <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#getLastUsed()} (if this class of the object implements
+     * {@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>
diff --git a/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java b/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
index b434ca9..30ca95d 100644
--- a/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
+++ b/src/main/java/org/apache/commons/pool2/impl/DefaultEvictionPolicy.java
@@ -23,13 +23,13 @@ import org.apache.commons.pool2.PooledObject;
  * pools. Objects will be evicted if the following conditions are met:
  * <ul>
  * <li>the object has been idle longer than
- *     {@link GenericObjectPool#getMinEvictableIdleTimeMillis()} /
- *     {@link GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()}</li>
+ *     {@link GenericObjectPool#getMinEvictableIdleTime()} /
+ *     {@link GenericKeyedObjectPool#getMinEvictableIdleTime()}</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#getSoftMinEvictableIdleTime()} /
+ *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleTime()}
  * </ul>
  * <p>
  * This class is immutable and thread-safe.
@@ -44,9 +44,8 @@ public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
     @Override
     public boolean evict(final EvictionConfig config, final PooledObject<T> underTest,
             final int idleCount) {
-
-        return (config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
+        return (config.getIdleSoftEvictTimeDuration().compareTo(underTest.getIdleTime()) < 0 &&
                 config.getMinIdle() < idleCount) ||
-                config.getIdleEvictTime() < underTest.getIdleTimeMillis();
+                config.getIdleEvictTimeDuration().compareTo(underTest.getIdleTime()) < 0;
     }
 }
diff --git a/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java b/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
index b604a20..e5fa53a 100644
--- a/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
+++ b/src/main/java/org/apache/commons/pool2/impl/DefaultPooledObject.java
@@ -17,6 +17,8 @@
 package org.apache.commons.pool2.impl;
 
 import java.io.PrintWriter;
+import java.time.Clock;
+import java.time.Instant;
 import java.util.Deque;
 
 import org.apache.commons.pool2.PooledObject;
@@ -38,11 +40,13 @@ 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 createTimeMillis = System.currentTimeMillis();
-    private volatile long lastBorrowTimeMillis = createTimeMillis;
-    private volatile long lastUseTimeMillis = createTimeMillis;
-    private volatile long lastReturnTimeMillis = createTimeMillis;
-    private volatile boolean logAbandoned = false;
+    private final Clock systemClock = Clock.systemUTC();
+    private final Instant createTimeInstant = now();
+
+    private volatile Instant lastBorrowInstant = createTimeInstant;
+    private volatile Instant lastUseInstant = createTimeInstant;
+    private volatile Instant lastReturnInstant = createTimeInstant;
+    private volatile boolean logAbandoned;
     private volatile CallStack borrowedBy = NoOpCallStack.INSTANCE;
     private volatile CallStack usedBy = NoOpCallStack.INSTANCE;
     private volatile long borrowedCount;
@@ -66,8 +70,8 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     public synchronized boolean allocate() {
         if (state == PooledObjectState.IDLE) {
             state = PooledObjectState.ALLOCATED;
-            lastBorrowTimeMillis = System.currentTimeMillis();
-            lastUseTimeMillis = lastBorrowTimeMillis;
+            lastBorrowInstant = now();
+            lastUseInstant = lastBorrowInstant;
             borrowedCount++;
             if (logAbandoned) {
                 borrowedBy.fillInStackTrace();
@@ -85,16 +89,15 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
 
     @Override
     public int compareTo(final PooledObject<T> other) {
-        final long lastActiveDiff = this.getLastReturnTime() - other.getLastReturnTime();
-        if (lastActiveDiff == 0) {
+        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);
         }
-        // handle int overflow
-        return (int)Math.min(Math.max(lastActiveDiff, Integer.MIN_VALUE), Integer.MAX_VALUE);
+        return compareTo;
     }
 
     /**
@@ -107,10 +110,9 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
      */
     @Override
     public synchronized boolean deallocate() {
-        if (state == PooledObjectState.ALLOCATED ||
-                state == PooledObjectState.RETURNING) {
+        if (state == PooledObjectState.ALLOCATED || state == PooledObjectState.RETURNING) {
             state = PooledObjectState.IDLE;
-            lastReturnTimeMillis = System.currentTimeMillis();
+            lastReturnInstant = now();
             borrowedBy.clear();
             return true;
         }
@@ -137,14 +139,7 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
 
     @Override
     public long getActiveTimeMillis() {
-        // Take copies to avoid threading issues
-        final long rTime = lastReturnTimeMillis;
-        final long bTime = lastBorrowTimeMillis;
-
-        if (rTime > bTime) {
-            return rTime - bTime;
-        }
-        return System.currentTimeMillis() - bTime;
+        return getActiveTime().toMillis();
     }
 
     /**
@@ -158,13 +153,18 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     }
 
     @Override
+    public Instant getCreateInstant() {
+        return createTimeInstant;
+    }
+
+    @Override
     public long getCreateTime() {
-        return createTimeMillis;
+        return createTimeInstant.toEpochMilli();
     }
 
     @Override
     public long getIdleTimeMillis() {
-        final long elapsed = System.currentTimeMillis() - lastReturnTimeMillis;
+        final long elapsed = System.currentTimeMillis() - lastReturnInstant.toEpochMilli();
         // 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)
@@ -173,18 +173,18 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
 
     @Override
     public long getLastBorrowTime() {
-        return lastBorrowTimeMillis;
+        return lastBorrowInstant.toEpochMilli();
     }
 
     @Override
     public long getLastReturnTime() {
-        return lastReturnTimeMillis;
+        return lastReturnInstant.toEpochMilli();
     }
 
     /**
      * 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#getLastUsed()} and
+     * the maximum of {@link TrackedUse#getLastUsedInstant()} and
      * {@link #getLastBorrowTime()}; otherwise this method gives the same
      * value as {@link #getLastBorrowTime()}.
      *
@@ -192,10 +192,24 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
      */
     @Override
     public long getLastUsedTime() {
+        return getLastUsedInstant().toEpochMilli();
+    }
+
+    /**
+     * 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 Instant getLastUsedInstant() {
         if (object instanceof TrackedUse) {
-            return Math.max(((TrackedUse) object).getLastUsed(), lastUseTimeMillis);
+            return PoolImplUtils.max(((TrackedUse) object).getLastUsedInstant(), lastUseInstant);
         }
-        return lastUseTimeMillis;
+        return lastUseInstant;
     }
 
     @Override
@@ -213,7 +227,7 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     }
 
     /**
-     * Sets the state to {@link PooledObjectState#INVALID INVALID}
+     * Sets the state to {@link PooledObjectState#INVALID INVALID}.
      */
     @Override
     public synchronized void invalidate() {
@@ -221,7 +235,7 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
     }
 
     /**
-     * Marks the pooled object as abandoned.
+     * Marks the pooled object as {@link PooledObjectState#ABANDONED ABANDONED}.
      */
     @Override
     public synchronized void markAbandoned() {
@@ -229,13 +243,22 @@ 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);
@@ -275,7 +298,6 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
             state = PooledObjectState.EVICTION;
             return true;
         }
-
         return false;
     }
 
@@ -294,8 +316,9 @@ public class DefaultPooledObject<T> implements PooledObject<T> {
 
     @Override
     public void use() {
-        lastUseTimeMillis = System.currentTimeMillis();
+        lastUseInstant = now();
         usedBy.fillInStackTrace();
     }
 
-}
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
index cd4819a..307db1e 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericKeyedObjectPool.java
@@ -17,6 +17,7 @@
 package org.apache.commons.pool2.impl;
 
 import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.HashMap;
@@ -1516,9 +1517,6 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
         return objectDeque;
     }
 
-
-    //--- internal attributes --------------------------------------------------
-
     /**
      * Recovers abandoned objects which have been checked out but
      * not used since longer than the removeAbandonedTimeout.
@@ -1531,16 +1529,14 @@ public class GenericKeyedObjectPool<K, T> extends BaseGenericObjectPool<T>
             final Map<IdentityWrapper<T>, PooledObject<T>> allObjects = pool.getValue().getAllObjects();
 
             // Generate a list of abandoned objects to remove
-            final long nowMillis = System.currentTimeMillis();
-            final long timeoutMillis =
-                    nowMillis - abandonedConfig.getRemoveAbandonedTimeoutDuration().toMillis();
+            final Instant timeout = Instant.now().minus(abandonedConfig.getRemoveAbandonedTimeoutDuration());
             final ArrayList<PooledObject<T>> remove = new ArrayList<>();
             final Iterator<PooledObject<T>> it = allObjects.values().iterator();
             while (it.hasNext()) {
                 final PooledObject<T> pooledObject = it.next();
                 synchronized (pooledObject) {
                     if (pooledObject.getState() == PooledObjectState.ALLOCATED &&
-                            pooledObject.getLastUsedTime() <= timeoutMillis) {
+                            pooledObject.getLastUsedInstant().compareTo(timeout) <= 0) {
                         pooledObject.markAbandoned();
                         remove.add(pooledObject);
                     }
diff --git a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
index 01b352a..e39f34f 100644
--- a/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
+++ b/src/main/java/org/apache/commons/pool2/impl/GenericObjectPool.java
@@ -17,6 +17,7 @@
 package org.apache.commons.pool2.impl;
 
 import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -56,7 +57,7 @@ import org.apache.commons.pool2.UsageTracking;
  * The pool can also be configured to detect and remove "abandoned" objects,
  * i.e. objects that have been checked out of the pool but neither used nor
  * returned before the configured
- * {@link AbandonedConfig#getRemoveAbandonedTimeout() removeAbandonedTimeout}.
+ * {@link AbandonedConfig#getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout}.
  * Abandoned object removal can be configured to happen when
  * {@code borrowObject} is invoked and the pool is close to starvation, or
  * it can be executed by the idle object evictor, or both. If pooled objects
@@ -1053,9 +1054,6 @@ public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
         ensureMinIdle();
     }
 
-
-    // --- internal attributes -------------------------------------------------
-
     /**
      * Recovers abandoned objects which have been checked out but
      * not used since longer than the removeAbandonedTimeout.
@@ -1065,16 +1063,14 @@ public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
     @SuppressWarnings("resource") // PrintWriter is managed elsewhere
     private void removeAbandoned(final AbandonedConfig abandonedConfig) {
         // Generate a list of abandoned objects to remove
-        final long nowMillis = System.currentTimeMillis();
-        final long timeoutMillis =
-                nowMillis - abandonedConfig.getRemoveAbandonedTimeoutDuration().toMillis();
+        final Instant timeout = Instant.now().minus(abandonedConfig.getRemoveAbandonedTimeoutDuration());
         final ArrayList<PooledObject<T>> remove = new ArrayList<>();
         final Iterator<PooledObject<T>> it = allObjects.values().iterator();
         while (it.hasNext()) {
             final PooledObject<T> pooledObject = it.next();
             synchronized (pooledObject) {
                 if (pooledObject.getState() == PooledObjectState.ALLOCATED &&
-                        pooledObject.getLastUsedTime() <= timeoutMillis) {
+                        pooledObject.getLastUsedInstant().compareTo(timeout) <= 0) {
                     pooledObject.markAbandoned();
                     remove.add(pooledObject);
                 }
@@ -1095,6 +1091,7 @@ public class GenericObjectPool<T> extends BaseGenericObjectPool<T>
             }
         }
     }
+
     /**
      * {@inheritDoc}
      * <p>
diff --git a/src/main/java/org/apache/commons/pool2/impl/PoolImplUtils.java b/src/main/java/org/apache/commons/pool2/impl/PoolImplUtils.java
index 5aebe55..a08a992 100644
--- a/src/main/java/org/apache/commons/pool2/impl/PoolImplUtils.java
+++ b/src/main/java/org/apache/commons/pool2/impl/PoolImplUtils.java
@@ -20,6 +20,7 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.time.Duration;
+import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -163,6 +164,30 @@ class PoolImplUtils {
     }
 
     /**
+     * Returns the greater of two {@code Instant} values. That is, the result is the argument closer to the value of
+     * {@link Instant#MAX}. If the arguments have the same value, the result is that same value.
+     *
+     * @param a an argument.
+     * @param b another argument.
+     * @return the larger of {@code a} and {@code b}.
+     */
+    static Instant max(final Instant a, final Instant b) {
+        return a.compareTo(b) > 0 ? a : b;
+    }
+
+    /**
+     * Returns the smaller of two {@code Instant} values. That is, the result is the argument closer to the value of
+     * {@link Instant#MIN}. If the arguments have the same value, the result is that same value.
+     *
+     * @param a an argument.
+     * @param b another argument.
+     * @return the smaller of {@code a} and {@code b}.
+     */
+    static Instant min(final Instant a, final Instant b) {
+        return a.compareTo(b) < 0 ? a : b;
+    }
+
+    /**
      * Converts a {@link TimeUnit} to a {@link ChronoUnit}.
      *
      * @param timeUnit A TimeUnit.
@@ -200,4 +225,5 @@ class PoolImplUtils {
     static Duration toDuration(final long amount, final TimeUnit timeUnit) {
         return Duration.of(amount, PoolImplUtils.toChronoUnit(timeUnit));
     }
+
 }
diff --git a/src/test/java/org/apache/commons/pool2/impl/TestAbandonedObjectPool.java b/src/test/java/org/apache/commons/pool2/impl/TestAbandonedObjectPool.java
index e042d8d..4e198c7 100644
--- a/src/test/java/org/apache/commons/pool2/impl/TestAbandonedObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/impl/TestAbandonedObjectPool.java
@@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
 import java.lang.management.ManagementFactory;
 import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.Set;
@@ -44,6 +45,8 @@ import org.junit.jupiter.api.Test;
 
 class PooledTestObject implements TrackedUse {
     private static final AtomicInteger hash = new AtomicInteger();
+    private static final Instant INSTANT_0 = Instant.ofEpochMilli(0);
+    private static final Instant INSTANT_1 = Instant.ofEpochMilli(1);
     private boolean active = false;
     private boolean destroyed = false;
     private int _hash = 0;
@@ -81,6 +84,17 @@ class PooledTestObject implements TrackedUse {
     }
 
     @Override
+    public Instant getLastUsedInstant() {
+        if (_abandoned) {
+            // Abandoned object sweep will occur no matter what the value of removeAbandonedTimeout,
+            // because this indicates that this object was last used decades ago
+            return INSTANT_1;
+        }
+        // Abandoned object sweep won't clean up this object
+        return INSTANT_0;
+    }
+
+    @Override
     public int hashCode() {
         return _hash;
     }
diff --git a/src/test/java/org/apache/commons/pool2/impl/TestPoolImplUtils.java b/src/test/java/org/apache/commons/pool2/impl/TestPoolImplUtils.java
index 3d9370e..3abaa20 100644
--- a/src/test/java/org/apache/commons/pool2/impl/TestPoolImplUtils.java
+++ b/src/test/java/org/apache/commons/pool2/impl/TestPoolImplUtils.java
@@ -16,9 +16,10 @@
  */
 package org.apache.commons.pool2.impl;
 
-
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.time.Instant;
+
 import org.apache.commons.pool2.BasePooledObjectFactory;
 import org.apache.commons.pool2.PooledObject;
 import org.junit.jupiter.api.Test;
@@ -26,12 +27,11 @@ import org.junit.jupiter.api.Test;
 public class TestPoolImplUtils {
 
     @SuppressWarnings("unused")
-    private abstract static class FactoryAB<A,B>
-            extends BasePooledObjectFactory<B> {
+    private abstract static class FactoryAB<A, B> extends BasePooledObjectFactory<B> {
         // empty by design
     }
 
-    private abstract static class FactoryBA<A,B> extends FactoryAB<B,A> {
+    private abstract static class FactoryBA<A, B> extends FactoryAB<B, A> {
         // empty by design
     }
 
@@ -40,11 +40,11 @@ public class TestPoolImplUtils {
     }
 
     @SuppressWarnings("unused")
-    private abstract static class FactoryDE<D,E> extends FactoryC<D>{
+    private abstract static class FactoryDE<D, E> extends FactoryC<D> {
         // empty by design
     }
 
-    private abstract static class FactoryF<F> extends FactoryDE<Long,F>{
+    private abstract static class FactoryF<F> extends FactoryDE<Long, F> {
         // empty by design
     }
 
@@ -53,6 +53,7 @@ public class TestPoolImplUtils {
         public Long create() throws Exception {
             return null;
         }
+
         @Override
         public PooledObject<Long> wrap(final Long obj) {
             return null;
@@ -64,6 +65,7 @@ public class TestPoolImplUtils {
         public String create() throws Exception {
             return null;
         }
+
         @Override
         public PooledObject<String> wrap(final String obj) {
             return null;
@@ -81,4 +83,24 @@ public class TestPoolImplUtils {
         final Class<?> result = PoolImplUtils.getFactoryType(SimpleFactory.class);
         assertEquals(String.class, result);
     }
+
+    @Test
+    public void testMaxInstants() {
+        final Instant instant0 = Instant.ofEpochMilli(0);
+        final Instant instant1 = Instant.ofEpochMilli(1);
+        assertEquals(instant1, PoolImplUtils.max(instant0, instant1));
+        assertEquals(instant1, PoolImplUtils.max(instant1, instant0));
+        assertEquals(instant1, PoolImplUtils.max(instant1, instant1));
+        assertEquals(instant0, PoolImplUtils.max(instant0, instant0));
+    }
+
+    @Test
+    public void testMinInstants() {
+        final Instant instant0 = Instant.ofEpochMilli(0);
+        final Instant instant1 = Instant.ofEpochMilli(1);
+        assertEquals(instant0, PoolImplUtils.min(instant0, instant1));
+        assertEquals(instant0, PoolImplUtils.min(instant1, instant0));
+        assertEquals(instant1, PoolImplUtils.min(instant1, instant1));
+        assertEquals(instant0, PoolImplUtils.min(instant0, instant0));
+    }
 }