Understanding Java Object Pooling: Commons Pool 2, Jedis Integration, and HikariCP Performance
This article explains Java object pooling concepts, introduces the Commons Pool 2 library, demonstrates its use with Redis via Jedis, compares performance with JMH benchmarks, and discusses the high‑performance HikariCP database connection pool, including configuration tips and common interview questions.
Object pooling is a technique to reuse expensive resources such as database connections, thread resources, or TCP sockets, reducing creation cost and improving system performance.
The Apache Commons Pool 2 library provides a generic object pool implementation. To use it, add the Maven dependency:
org.apache.commons
commons-pool2
2.11.1The core class GenericObjectPool is created by passing a factory and a configuration:
public GenericObjectPool(
final PooledObjectFactory
factory,
final GenericObjectPoolConfig
config)Jedis, the popular Redis client, uses Commons Pool to manage its connections. The factory method that creates pooled Jedis objects looks like this:
@Override
public PooledObject
makeObject() throws Exception {
Jedis jedis = null;
try {
jedis = new Jedis(jedisSocketFactory, clientConfig);
// main time‑consuming operation
jedis.connect();
// return wrapped object
return new DefaultPooledObject<>(jedis);
} catch (JedisException je) {
if (jedis != null) {
try { jedis.quit(); } catch (RuntimeException e) { logger.warn("Error while QUIT", e); }
try { jedis.close(); } catch (RuntimeException e) { logger.warn("Error while close", e); }
}
throw je;
}
}When borrowing an object, the pool first checks the idle queue and creates a new instance only if none are available:
public T borrowObject(final Duration borrowMaxWaitDuration) throws Exception {
// ... omitted lines
while (p == null) {
create = false;
// first try to get from idle queue
p = idleObjects.pollFirst();
// if none, create new via factory
if (p == null) {
p = create();
if (p != null) {
create = true;
}
}
// ... omitted lines
}
// ... omitted lines
}The configuration class GenericObjectPoolConfig contains many tunable parameters, for example:
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
private boolean lifo = DEFAULT_LIFO;
private boolean fairness = DEFAULT_FAIRNESS;
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS;
private long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private long evictorShutdownTimeoutMillis = DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT_MILLIS;
private long softMinEvictableIdleTimeMillis = DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
private int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
private EvictionPolicy
evictionPolicy = null;
private String evictionPolicyClassName = DEFAULT_EVICTION_POLICY_CLASS_NAME;
private boolean testOnCreate = DEFAULT_TEST_ON_CREATE;
private boolean testOnBorrow = DEFAULT_TEST_ON_BORROW;
private boolean testOnReturn = DEFAULT_TEST_ON_RETURN;
private boolean testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
private long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
private boolean blockWhenExhausted = DEFAULT_BLOCK_WHEN_EXHAUSTED;Key parameters such as maxTotal , maxIdle , minIdle , and maxWaitMillis control pool size, idle object limits, and how long a thread waits for a resource before failing.
A JMH benchmark comparing a pooled Jedis connection with a direct Jedis instance shows roughly a five‑fold throughput improvement when using the pool:
@Fork(2)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@BenchmarkMode(Mode.Throughput)
public class JedisPoolVSJedisBenchmark {
JedisPool pool = new JedisPool("localhost", 6379);
@Benchmark
public void testPool() {
Jedis jedis = pool.getResource();
jedis.set("a", UUID.randomUUID().toString());
jedis.close();
}
@Benchmark
public void testJedis() {
Jedis jedis = new Jedis("localhost", 6379);
jedis.set("a", UUID.randomUUID().toString());
jedis.close();
}
// ... omitted lines
}HikariCP, the default connection pool in Spring Boot 2.x, achieves even higher performance by using three main optimizations: replacing ArrayList with FastList , simplifying bytecode with Javassist (e.g., using invokestatic instead of invokevirtual ), and employing a lock‑free ConcurrentBag .
Configuration advice for HikariCP includes setting an appropriate maximumPoolSize (typically 20‑50 for most databases), avoiding an oversized minimumIdle , and relying on JDBC4's Connection.isValid() for health checks instead of many test flags.
The article also introduces the concept of a "Result Cache Pool", drawing parallels between caching and pooling, and suggests that similar pooling strategies can be applied to HTTP clients, RPC frameworks, and thread pools.
In summary, understanding and tuning object pools—whether generic pools, Redis pools, or high‑performance JDBC pools like HikariCP—can dramatically improve application throughput and resource utilization.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.