skip to content

What is object pooling, and when does it make sense to use it in Java versus just letting the garbage collector handle short-lived objects?

level: juniorimportance: should knowfreq 55%

answer

  1. Pool resources, not values
  2. Threads, connections, big buffers = yes
  3. Generational GC + TLAB make allocation nearly free
  4. Pooling promotes garbage to old gen
  5. Pooling adds contention, stale state, leaks

basics

~20 s

Object pooling means keeping a set of reused objects instead of creating new ones each time. It is worth it only for expensive or scarce things like threads, database connections, or big buffers. For ordinary small objects, just let the garbage collector handle them.

solid answer

~40 s

Object pooling keeps a reusable set of pre-created objects and hands them out on request, returning them to the pool afterward instead of discarding them. It pays off when the object is expensive to create or represents a scarce resource: threads (thread pools), database or HTTP connections (connection pools), and large reusable byte buffers. For plain, cheap Java objects it is usually a mistake on a modern JVM, because the generational garbage collector makes allocating and collecting short-lived objects extremely cheap, while a pool adds synchronization, complexity, and the risk of stale or leaked state. The rule of thumb: pool only what is genuinely expensive to create or limited in supply; allocate everything else freely and let the GC reclaim it.

go deeper

for a junior

Can state that pooling reuses objects and that it's mainly for expensive resources like threads and connections, and that the GC handles ordinary objects.

for a middle

Explains why modern allocation is cheap (young-gen GC, short-lived objects die cheaply) and can list the downsides of pooling plain objects.

for a senior

Frames it as 'pool resources, not values', references TLABs/pointer-bump allocation and old-gen promotion, and weighs contention and stale-state risks against the supposed win.

for a principal

Sets policy: bans ad-hoc pooling in code review, reserves pooling for measured hot paths with scarce/expensive resources, and ties the decision to allocation profiling and GC pause budgets rather than folklore.

## What "object pooling" means In Java you normally create an object with `new`, use it, and then forget about it; once nothing references it, the **garbage collector (GC)** — the JVM subsystem that automatically reclaims memory of unreachable objects — frees its memory. **Object pooling** is a different strategy: instead of throwing an object away, you keep a collection (the *pool*) of already-created objects. When code needs one, it *borrows* from the pool; when done, it *returns* it for the next borrower. The idea is to amortize a one-time creation cost over many uses. ## Why pooling was once a default Very old JVMs allocated and collected objects slowly, so creating millions of short-lived objects was genuinely costly. Pooling avoided that cost. That historical context is why a lot of older advice says "reuse objects." On modern JVMs this advice is largely obsolete for ordinary objects. ## Why allocation is cheap now Two JVM features make creating short-lived objects nearly free: - **Generational GC.** The heap is split into a *young generation* (where new objects are born) and an *old generation* (long-lived objects). Most objects die young. Young-generation collection (a *minor GC*) only walks the small set of objects that are still alive and copies them; it does **not** scan the dead ones individually. So allocating an object that quickly becomes garbage costs almost nothing to clean up. - **TLAB (Thread-Local Allocation Buffer) and pointer-bump allocation.** Each thread gets a private slice of the young generation. Allocating is just incrementing a pointer ("bump the pointer") within that buffer — a handful of machine instructions, no locking. This is why allocation throughput on the JVM is enormous. ## Why pooling plain objects often *hurts* If you pool ordinary objects you fight the GC instead of helping it: 1. **You promote garbage to the old generation.** A pooled object lives a long time, so the GC moves it into the old generation. Old-generation collections are rarer but more expensive, and now your "reused" objects are sitting there adding pressure. 2. **You add synchronization and contention.** A shared pool needs thread-safe borrow/return, which introduces locking or CAS contention — often slower than the lock-free TLAB allocation it replaced. 3. **You add bugs and complexity.** Pooled objects must be reset to a clean state; forgetting leaves *stale state*. Forgetting to return them is effectively a memory leak. Returning one twice, or using it after return, causes corruption. ## When pooling *is* correct Pool only **expensive-to-create or scarce resources**, where the creation cost dwarfs the bookkeeping cost: - **Threads** — creating an OS thread is expensive; hence thread pools (`ExecutorService`). - **Connections** — TCP handshake, authentication, TLS make DB/HTTP connections costly; hence connection pools (HikariCP, etc.). - **Large buffers** — big `byte[]`/direct `ByteBuffer`s are expensive and can stress the GC; pooling them (e.g. Netty's pooled allocators) is justified. ## Rule of thumb > Pool a *resource* (something with an expensive lifecycle or a hard supply limit). Do **not** pool a *value* (an ordinary, cheap object). For ordinary objects, allocate freely and trust the generational GC.

  • Name three things that are legitimately worth pooling in Java.
    Threads (thread pools / ExecutorService), database or network connections (connection pools like HikariCP), and large reusable buffers (e.g. direct ByteBuffers, Netty's pooled allocator).
  • Why doesn't pooling a small ordinary object usually help on a modern JVM?
    Allocation is a cheap pointer-bump in a TLAB and short-lived objects are collected almost for free by the young-generation GC, while the pool adds synchronization, complexity, and keeps objects alive long enough to be promoted to the more expensive old generation.

saying these in an interview costs you the question

  • Claiming pooling is always faster because 'object creation is slow' (true on 1990s JVMs, not today)
  • Pooling tiny value objects like points, DTOs, or wrapper types
  • Forgetting that pooling keeps objects alive and promotes them to old gen
  • Assuming a pool is automatically thread-safe or zero-overhead

context