Java Reference Types: Strong, Weak, Soft, Phantom
How Java reference strengths interact with the garbage collector to enable caches, listeners, and cleanup.
Java Reference Types: Strong, Weak, Soft, Phantom
Garbage collection feels automatic, but Java gives you a dial to influence it. An ordinary variable is a strong reference that pins an object in memory. The java.lang.ref package adds three weaker grades — soft, weak, and phantom — that let the garbage collector reclaim an object even while you still hold a handle to it. These reference types are the foundation of memory-sensitive caches, leak-free listener registries, and reliable resource cleanup.
Reachability decides who lives
The garbage collector keeps an object alive as long as it is reachable from a GC root (a live thread, a static field, a stack variable). The strength of the references on the path to an object determines its reachability class, and that class decides its fate when memory is needed.
| Reference | get() returns object? | GC keeps it alive? | Typical use |
|---|---|---|---|
| Strong | Always | Always (while strongly reachable) | Ordinary variables and fields |
| Soft | Until memory runs low | Until the heap is under pressure | Memory-sensitive caches |
| Weak | Until the next GC clears it | No | Canonicalizing maps, listeners |
| Phantom | Never (always null) | No | Post-mortem cleanup |
The order from strongest to weakest is: strong → soft → weak → phantom. When several references of different strengths point at one object, the strongest one wins — a single strong reference is enough to keep an object alive forever.
Strong references: the default
Every reference you write without the java.lang.ref API is strong. As long as one strong reference is reachable, the object cannot be collected — this is the source of most memory leaks, where a forgotten entry in a long-lived collection keeps objects alive indefinitely.
List<byte[]> cache = new ArrayList<>();
cache.add(new byte[10_000_000]); // 10 MB pinned by a strong reference
// Nothing here can be collected until 'cache' itself becomes unreachable.Soft references: memory-sensitive caches
A SoftReference lets the collector reclaim the object only when the heap is running low. While memory is plentiful, get() keeps returning the object, which makes soft references the natural fit for caches that should shrink under pressure rather than cause an OutOfMemoryError.
SoftReference<BufferedImage> ref = new SoftReference<>(loadThumbnail(path));
BufferedImage cached = ref.get();
if (cached == null) { // GC reclaimed it under memory pressure
cached = loadThumbnail(path); // recompute and re-wrap
ref = new SoftReference<>(cached);
}
return cached;The JVM guarantees that all soft references to softly-reachable objects are cleared before it throws OutOfMemoryError, so a soft-reference cache is the last thing sacrificed, not the first.
Weak references: maps and listeners
A WeakReference does not delay collection at all. The instant an object is only weakly reachable, it becomes eligible for GC and get() will return null after the next collection cycle. This is exactly what you want for keys in a cache that should disappear when no one else uses them — which is what WeakHashMap is built on.
WeakHashMap<Widget, Metadata> sidecar = new WeakHashMap<>();
sidecar.put(widget, metadata);
// When 'widget' is no longer strongly referenced elsewhere, the entry
// vanishes automatically — no manual remove(), no leak.Pairing a reference with a ReferenceQueue lets you be notified when the referent is collected: the GC enqueues the cleared reference so you can run follow-up logic.
ReferenceQueue<Widget> queue = new ReferenceQueue<>();
WeakReference<Widget> ref = new WeakReference<>(widget, queue);
// later, on a cleanup thread:
Reference<?> dead = queue.remove(); // blocks until a referent is collectedPhantom references: post-mortem cleanup
A PhantomReference is the weakest grade and the most specialized. Its get() always returns null, so you can never resurrect the object through it. Its sole purpose is to be enqueued after the object has been collected, giving you a safe hook to release native resources — the modern, reliable replacement for the deprecated finalize().
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(resource, queue);
// A background thread drains the queue and frees the off-heap buffer
// only once the JVM confirms the object is truly gone.The JDK's own java.lang.ref.Cleaner (Java 9+) is built on phantom references and is what you should reach for in real code instead of managing the queue by hand.
A worked example: all four strengths in one run
This program creates one object held only by each kind of reference, forces a garbage collection with System.gc(), and reports what survived. It also wires ReferenceQueues to the weak and phantom references so you can watch the GC notify you of each death.
What to take from the run:
- The strong reference printed the same
Resource(kept-alive)at the start and the end. Despite twoSystem.gc()calls in between, a strongly reachable object is never a collection candidate — strong references always win. weak.get()returnedResource(weakly-held)before the GC butnullafterward. Once the only strong link (onlyWeaklyHeld) was set tonull, the object was merely weakly reachable, so the very next collection cleared the weak reference.weak ref enqueued? : trueconfirms the GC pushed the clearedWeakReferenceonto itsReferenceQueue. That enqueue is the notification mechanism — it is howWeakHashMapand listener registries learn that an entry can be purged.soft.get()still returnedResource(cache-entry)aftergc(). The heap was under no pressure, so the collector kept the softly-reachable object alive — exactly the behavior that makes soft references suitable for caches that only shrink when memory is tight.phantom.get()printednulleven before any collection, yetphantom enqueued? : trueshows it was still queued once its referent died. A phantom reference never hands back the object; it exists purely to signal after the fact that cleanup can safely run.
Practice
You need a cache that holds computed values to speed up your program but should automatically release them rather than cause an OutOfMemoryError when the heap runs low. Which reference type fits best?