Object Pooling
Object pooling reuses preallocated instances instead of allocating per use, trading initialization cost for pool management — and in managed runtimes often fighting the collector it meant to help.
Definition
An object pool maintains a set of initialized instances, lending them out and taking them back instead of constructing and discarding. Pools win when construction is expensive, when allocation triggers GC pressure, or when resources are scarce — connections, buffers, threads. In modern generational runtimes, pooling small short-lived objects can be slower than allocating them.
Context and scope
Applied for connection pools, buffer pools, and heavyweight framework objects. Tuned with max size, idle eviction, health checks, and borrow timeouts.
Common misconceptions
- Pooling always beats allocation in GC languages — young-generation allocation is pointer-bump cheap; pooling can promote garbage and enlarge the old generation
- A pool prevents leaks — pools cause leaks when borrowers never return; discipline and timeouts are still required
- Pool size should be as large as possible — oversized pools hold memory and resources idle and hurt downstream capacity
Anti-patterns
- Pooling trivially cheap objects and paying synchronization plus stale-state bugs for negative gains
- Returning objects without resetting state and leaking data between borrowers
- Sharing one pool for objects with different initialization costs and starving the cheap ones behind the expensive
Related concepts
Authoritative source
What it is
Construction and teardown have costs: object initialization, resource acquisition, collector bookkeeping. A pool amortizes both by cycling instances through borrowers.
How it works
The pool exposes acquire and release with semantics for max size, borrow timeout, and validation on acquire or on return. Reset happens on release or acquisition — the safer point — clearing references and state. Idle eviction shrinks the pool under low load; health validation drops broken instances.
When to pool
Genuinely expensive resources: database connections, native buffers, threads, large byte arrays. Measure first in managed runtimes: escape-analysis and TLAB allocation make many small objects nearly free to allocate and collect.
Pitfalls
Stale state between borrowers is the classic security bug — reset lists must be explicit and tested. Pooling arrays that grow means borrowers hold max-size memory always. In generational collectors, pooled objects live long, inflating the old generation and pause work — the pool becomes GC pressure it claimed to remove.