Backend Development 17 min read

Analysis of Apache Commons-Pool2 Object Pooling Implementation

The article examines Apache Commons‑Pool2’s object‑pool architecture, detailing its core interfaces (ObjectPool, PooledObjectFactory, PooledObject), the GenericObjectPool construction, FIFO/LIFO idle‑object deque management with lock‑based concurrency, borrowing and returning workflows, self‑protection features like abandonment detection, and the performance tuning needed for high‑concurrency environments.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Analysis of Apache Commons-Pool2 Object Pooling Implementation

Introduction: The article discusses the importance of pooling technologies such as object pools, connection pools, and thread pools for reducing system overhead and improving performance, especially under high concurrency.

It focuses on analyzing the implementation of Apache Commons‑Pool2, a widely used library for managing object pools in Java.

Core components of Commons‑Pool2:

ObjectPool : Manages the lifecycle of pooled objects and provides statistics on active and idle objects.

PooledObjectFactory : Responsible for creating, initializing, destroying, and validating objects. The library provides BasePooledObjectFactory as a default abstract implementation.

PooledObject : Wraps the actual object and stores additional state information such as creation time and activation time. Implementations include DefaultPooledObject and PoolSoftedObject (uses SoftReference ).

The article then details the object‑pool workflow, including the interface methods addObject , borrowObject , invalidateObject , and returnObject (code block shown below).

/** 向对象池中增加对象实例 */
void addObject() throws Exception, IllegalStateException, UnsupportedOperationException;
/** 从对象池中获取对象 */
T borrowObject() throws Exception, NoSuchElementException, IllegalStateException;
/** 失效非法的对象 */
void invalidateObject(T obj) throws Exception;
/** 释放对象至对象池 */
void returnObject(T obj) throws Exception;

Construction of a GenericObjectPool requires a PooledObjectFactory and optionally a configuration object. Example constructors are provided.

public GenericObjectPool(final PooledObjectFactory
factory) {
    this(factory, new GenericObjectPoolConfig
());
}
public GenericObjectPool(final PooledObjectFactory
factory, final GenericObjectPoolConfig
config) {
    super(config, ONAME_BASE, config.getJmxNamePrefix());
    if (factory == null) {
        jmxUnregister(); // tidy up
        throw new IllegalArgumentException("factory may not be null");
    }
    this.factory = factory;
    idleObjects = new LinkedBlockingDeque<>(config.getFairness());
    setConfig(config);
}

The pool uses a LinkedBlockingDeque to store idle objects, supporting both FIFO and LIFO policies. The underlying queue implementation relies on a main lock and two conditions ( notFull , notEmpty ) to coordinate concurrent access.

public boolean offerFirst(final E e, final long timeout, final TimeUnit unit) throws InterruptedException {
    Objects.requireNonNull(e, "e");
    long nanos = unit.toNanos(timeout);
    lock.lockInterruptibly();
    try {
        while (!linkFirst(e)) {
            if (nanos <= 0) return false;
            nanos = notFull.awaitNanos(nanos);
        }
        return true;
    } finally {
        lock.unlock();
    }
}

Borrowing an object follows several steps: attempt to poll from the deque, create a new object if necessary, optionally block when exhausted, allocate the object, activate it via the factory, and validate it according to the testOnBorrow setting.

p = idleObjects.pollFirst();
if (p == null) {
    p = create();
    if (p != null) create = true;
}
if (blockWhenExhausted) {
    if (p == null) {
        if (borrowMaxWaitMillis < 0) {
            p = idleObjects.takeFirst();
        } else {
            p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
        }
    }
}

Returning an object involves marking it as RETURNING , optional validation ( testOnReturn ), passivation, deallocation to IDLE , and possibly destroying the object if the pool exceeds its maximum idle count.

if (!p.allocate()) {
    p = null;
}
if (p != null) {
    try {
        factory.activateObject(p);
    } catch (Exception e) {
        destroy(p, DestroyMode.NORMAL);
    }
    if (p != null && getTestOnBorrow()) {
        boolean validate = false;
        try {
            validate = factory.validateObject(p);
        } catch (Throwable t) {
            PoolUtils.checkRethrow(t);
        }
        if (!validate) {
            destroy(p, DestroyMode.NORMAL);
        }
    }
}

The article also discusses self‑protection mechanisms in Commons‑Pool2, such as threshold‑based abandonment detection and asynchronous eviction threads, which help prevent resource leaks when client code fails to return objects.

Finally, the author notes that while pooling improves resource reuse, the internal locking and configuration of pool sizes can affect performance in highly concurrent scenarios, requiring careful tuning.

Javaconcurrencyresource managementCommons Pool2Object Pooling
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.