Skip to content

Repository files navigation

APACHE v2 License Latest Release Javadocs Codacy

generic-object-pool

generic-object-pool is a lightweight generic object pool, providing object lifecycle management, metrics, claim / release mechanism and object invalidation, as well as auto initialize a core pool and auto expiry policies.

Origins

The work that became generic-object-pool began in June 2019 with a fork of KBOP, created by Jeremy Unruh. It was subsequently modernized and extensively redesigned for Simple Java Mail's SMTP connection pooling, and has continued as an independent library. KBOP's original work is used under the MIT License; its complete copyright and license notice is included in NOTICE.txt.

Setup

Maven Dependency Setup

<dependency>
	<groupId>com.github.bbottema</groupId>
	<artifactId>generic-object-pool</artifactId>
	<version>2.4.3</version>
</dependency>

For JPMS applications, the published JAR declares the stable automatic module name org.bbottema.genericobjectpool.

Release Notes

2.4.3 (7 September 2026)

  • #20: Wake already-blocked claimers when invalidation frees capacity or the core pool adds replacements. These callers no longer need another release to make progress.
  • Concurrent invalidation of the same object now schedules cleanup only once and preserves pool counts.
  • Shutdown no longer observes the gap between invalidation and queued cleanup as an empty pool.
  • The public API, Java 8 baseline and JPMS module name are unchanged.

Usage

Creating pools

// basic pool with no eager loading and no expiry policy
PoolConfig<Foo> poolConfig = PoolConfig.<Foo>builder()
   .maxPoolsize(10)
   .build();

GenericObjectPool<Foo> pool = new GenericObjectPool<>(poolConfig, new MyFooAllocator());
// more advanced pool with eager loading and auto expiry
PoolConfig<Foo> poolConfig = PoolConfig.<AtomicReference<Integer>>builder()
   .corePoolsize(20) // keeps 20 objects eagerly allocated at all times
   .maxPoolsize(20)
   // deallocate after 30 seconds, but every time an object is claimed the expiry timeout is reset
   .expirationPolicy(new TimeoutSinceLastAllocationExpirationPolicy<Foo>(30, TimeUnit.SECONDS))
   .build();

GenericObjectPool<Foo> pool = new GenericObjectPool<>(poolConfig, new MyFooAllocator());

Claim / release API

Claiming objects from the pool (blocking):

// borrow an object and block until available
PoolableObject<Foo> obj = pool.claim();

Claiming objects from the pool (blocking until timeout):

PoolableObject<Foo> obj = pool.claim(1, TimeUnit.SECONDS); // null if timed out

Claiming an already available object matching a predicate:

PoolableObject<Foo> obj = pool.claimMatching(
	poolable -> poolable.idleAgeMs() >= TimeUnit.MINUTES.toMillis(5),
	1,
	TimeUnit.SECONDS);

if (obj != null) {
	try {
		obj.getAllocatedObject().ping();
		obj.release();
	} catch (IOException e) {
		obj.invalidate();
	}
}

The predicate is evaluated while the pool claim lock is held, so keep it fast and side-effect free. Run slow work such as ping/keep-alive checks after the object has been claimed.

Releasing Objects back to the Pool:

PoolableObject<Foo> obj = pool.claim();
obj.release(); // make available for reuse
// or
obj.invalidate(); // remove from pool, deallocating

Invalidation wakes ordinary callers that are already waiting for capacity. With a core size of zero, a waiting caller can create a replacement; a configured core pool also replenishes itself. Final cleanup remains asynchronous and need not finish before a replacement can be used. Matching claims still only take available objects: they do not allocate replacements themselves.

Shutting down a pool

Future<?> shutdownSequence = pool.shutdown();

// wait for shutdown to complete
shutdownSequence.get();
// until timeout
shutdownSequence.get(10, TimeUnit.SECONDS);

Creating your objects

Implementing a simple Allocator to create your objects when populating the pool either eagerly or lazily. Every method except allocate is optional:

static class FooAllocator extends Allocator<Foo> {
	/**
	 * Initial creation and initialization.
	 * Called when claim comes or when pool is eagerly loading for core size.
	 */
	@Override
	public AtomicReference<Integer> allocate() {
		return new Foo();
	}
}

More comprehensive life cycle management:

static class FooAllocator extends Allocator<Foo> {
	/**
	 * Initial creation and initialization.
	 * Called when claim comes or when pool is eagerly loading for core size.
	 */
	@Override
	public AtomicReference<Integer> allocate() {
		return new Foo();
	}
	
	/**
	 * Uninitialize an instance which has been released back to the pool, until it is claimed again.
	 */
	@Override
	protected void deallocateForReuse(Foo object) {
		object.putAtRest();
	}
	
	/**
	 * Reinitialize an object so it is ready to be claimed.
	 */
	@Override
	protected void allocateForReuse(Foo object) {
		object.reinitialize();
	}
	
	/**
	 * Clean up an object no longer needed by the pool.
	 */
	@Override
	protected void deallocate(Foo object) {
		object.clear();
	}
}

Metrics

PoolMetrics metrics = pool.getPoolMetrics();
metrics.getCurrentlyClaimed(); // currently claimed by threads and not released yet
metrics.getCurrentlyWaitingCount(); // currently waiting threads that want to claim
metrics.getCorePoolsize(); // number of instances to auto allocated (eager loading)
metrics.getMaxPoolsize(); // max number of objects allowed at all times
metrics.getCurrentlyAllocated(); // available + claimed objects
metrics.getTotalAllocated(); // total number of allocations during pool's existence
metrics.getTotalClaimed(); // total number of claims during pool's existence

For idle maintenance, PoolableObject#idleAgeMs() reports how long an object has been available for claiming. It returns 0 while the object is claimed.

If for some reason you need to have more control over how threads are created, you can provide you own ThreadFactory:

PoolConfig<Foo> poolConfig = PoolConfig.<AtomicReference<Integer>>builder()
   .threadFactory(new MyCustomThreadFactory())
   .build();

Other Expiry strategies

You can expire objects based on age since creation or age since last allocation. For these use:

  • TimeoutSinceCreationExpirationPolicy
  • TimeoutSinceLastAllocationExpirationPolicy

You can also spread the expiry around in a bandwidth to avoid having everything expire at the same time, hogging system resources. for these use:

  • SpreadedTimeoutSinceCreationExpirationPolicy
  • SpreadedTimeoutSinceLastAllocationExpirationPolicy

You can also combine multiple expirations, by passing instances of them as a set to:

  • CombinedExpirationPolicies

Finally, you can extend any of these or create your own from scratch by implementing:

  • ExpirationPolicy

To aid you in creating your own expiry policy, you can calculate and store an expiry age on the poolable object:

poolableObject.getExpiries().put(this, calculatedAge);
Long previouslyCalculateAge = poolableObject.getExpiries().get(this);

You can always extend one the abstract classes SpreadedTimeoutExpirationPolicy and TimeoutExpirationPolicy, which do this for you.

About

A flexible thread-safe generic object pool

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages