Strong, soft, weak and phantom references
The four reference strengths in Java, what the garbage collector does with each, how ReferenceQueue, Cleaner and WeakHashMap use them, why soft-reference caches backfire in production, and every way an object becomes eligible for GC.
On this page
🔗 What are strong, soft, weak and phantom references?
Java has four strengths of reference, and the strength tells the garbage collector how hard to fight for the object. A normal variable like Avatar a = new Avatar() is a strong reference: the object can't be collected while it's strongly reachable. Wrap it in a SoftReference, WeakReference or PhantomReference from java.lang.ref, and you're telling the collector "you may reclaim this; here's how I want to hear about it."
For this lesson, we'll rank the reachability levels, write each reference type in code, wire them to a ReferenceQueue, replace finalize() with Cleaner, and look hard at WeakHashMap and soft-reference caches. We'll finish with the ways an ordinary object becomes eligible for GC, including the cycle case people get wrong. Interviewers ask this to see whether you understand reachability, the idea the whole collector is built on, rather than a vague "the GC deletes things nobody uses".
Below the line (out of scope)
- Where references and objects live: Stack vs heap
- How the collector traces from GC roots and frees memory: How garbage collection works
- Why young collections don't look at old objects: Generational collection
- Classloader leaks and
ThreadLocal: Metaspace: where classes live
The 30-second answer
The GC keeps every object reachable from GC roots through ordinary strong references. A SoftReference lets the collector clear the object when memory gets tight; it's cleared at the GC's discretion and guaranteed cleared before an OutOfMemoryError. A WeakReference is cleared as soon as a GC finds the object only weakly reachable. A PhantomReference never returns its object (get() is always null); it's enqueued after the object becomes reclaimable so you can run cleanup, which is what java.lang.ref.Cleaner builds on and why finalize() is deprecated for removal. Any of them can be registered with a ReferenceQueue to find out when the GC cleared it. In practice: strong for almost everything, weak for metadata keyed by an object you don't own (WeakHashMap), Cleaner for native resources as a safety net behind close(), and soft references rarely, because size-bounded caches behave far better.
What you’ll learn
- Rank the reachability levels: strong, soft, weak, phantom, unreachable and say what the GC does at each
- Use
SoftReference,WeakReferenceandPhantomReferencewith aReferenceQueue, and know whyPhantomReference.get()always returnsnull - Replace
finalize()with **java.lang.ref.Cleaner** plus try-with-resources - Explain how
WeakHashMapdrops entries, and its traps with literal keys and values that point back at keys - Argue why soft-reference caches usually hurt in production and what to use instead
- List the ways an object becomes eligible for GC, including islands of isolation
Interview radar
Asked very often: What's the difference between a weak and a soft reference?
Core follow-up: When does an object become eligible for garbage collection? What about two objects that reference each other?
Production angle: Someone built an image cache on SoftReference. GC pauses got worse, not better. Why?
01Picture it first: the library desk
Imagine books on a library reading desk. A book you're holding is strongly reachable: nobody takes it. A book with a "please keep" note is soft: the librarian leaves it there until the shelves run short of space. A book left on the desk with no note is weak: the next time the librarian sweeps past, it goes back. A phantom reference is the slip the librarian hands you after the book is gone: you can't read the book through it, but it tells you to cancel your hold.
The ladder below orders those levels from strongest to weakest, with what the collector does at each step.
A ladder of reachability levels from GC roots: strongly reachable objects are never collected, softly reachable ones are cleared under memory pressure and before OutOfMemoryError, weakly reachable ones at the next GC that notices, phantom reachable ones are enqueued for cleanup, and unreachable ones are reclaimed
Notice that the level belongs to the object, not the reference. An object with one strong path and ten weak ones is strongly reachable. The java.lang.ref package defines each level by the strongest path that still exists: strongly reachable means some thread can reach it without passing through any reference object; softly reachable means only via soft references, and so on down.
02Under the hood: the four types in code
Try it yourself first
Before running this, predict the five lines of output. Which referents survive one System.gc(), and what does the phantom reference return?
import java.lang.ref.*;
public class RefDemo {
record Avatar(String userId, byte[] pixels) { }
public static void main(String[] args) throws Exception {
Avatar strong = new Avatar("u-1", new byte[1024]);
SoftReference<Avatar> soft = new SoftReference<>(new Avatar("u-2", new byte[1024]));
WeakReference<Avatar> weak = new WeakReference<>(new Avatar("u-3", new byte[1024]));
ReferenceQueue<Avatar> queue = new ReferenceQueue<>();
PhantomReference<Avatar> phantom =
new PhantomReference<>(new Avatar("u-4", new byte[1024]), queue);
System.gc(); // a request, not a command
Thread.sleep(100);
System.out.println("strong: " + strong.userId());
System.out.println("soft: " + (soft.get() == null ? "cleared" : soft.get().userId()));
System.out.println("weak: " + (weak.get() == null ? "cleared" : weak.get().userId()));
System.out.println("phantom: get() = " + phantom.get());
System.out.println("queued: " + (queue.remove(1000) == phantom));
}
}
On a typical JDK 21 run with default settings you'll see:
strong: u-1
soft: u-2
weak: cleared
phantom: get() = null
queued: true
Notice four things:
- Strong survived because a live local variable points at it.
- Soft survived because the heap is nearly empty. Nothing forced the collector to clear it.
- Weak was cleared by the first collection that found
u-3only weakly reachable. - Phantom returns
nulleven before the GC runs. Its only output is the enqueue, which arrived onqueue.
Now run it with -XX:+DisableExplicitGC. The JDK docs describe that flag as disabling processing of System.gc() calls, so nothing gets collected: weak still prints u-3 and queue.remove(1000) times out. That's the cleanest proof that System.gc() is a hint. Never build program logic on it; it's fine in a demo like this one.
Soft references: cleared under memory pressure
The SoftReference Javadoc makes exactly one hard promise: all soft references to softly reachable objects are cleared before the JVM throws OutOfMemoryError. Beyond that, the collector decides when, and implementations are encouraged to keep recently used ones. HotSpot's policy is spelled out in the GC tuning guide: a softly reachable object is kept for about 1,000 ms per MB of free heap since it was last accessed (-XX:SoftRefLRUPolicyMSPerMB=1000).
Weak references: cleared when the GC notices
The common phrasing "weak references die at the next GC" is close but not exact. A weak reference is cleared when a collection determines that the object is weakly reachable. A young collection that doesn't examine the old generation won't notice a weakly reachable object that has already been promoted, so it can sit there until the next old or mixed collection.
Phantom references: notice after the fact
A phantom reference is enqueued after the collector has determined its referent may be reclaimed, and since Java 9 the GC clears it at the same time. get() always returns null so you can't resurrect the object. Since Java 16, ref.refersTo(obj) lets you test identity without retrieving the referent. You almost never use PhantomReference directly; you use Cleaner, which wraps it.
03ReferenceQueue and WeakHashMap
A reference object can be registered with a ReferenceQueue when you create it. After the GC clears the reference, it appends the reference object (not the referent, which is gone or going) to the queue. Your code then polls the queue or blocks on remove() to do bookkeeping. The flow below follows one weak key from the moment you drop it to the moment its map entry disappears.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- How an ordinary object becomes eligible for GC
- Advanced: Cleaner instead of finalize()
- Advanced: why soft-reference caches backfire
- How to say it in the interview
From ₹299 · one-time payment · No auto-renewal
Interview questions for this topicComing soon
Further reading (12)
- java.lang.ref package summary: reachability and notification (Java SE 21)
- SoftReference (Java SE 21 API)
- WeakReference (Java SE 21 API)
- PhantomReference (Java SE 21 API)
- ReferenceQueue (Java SE 21 API)
- Reference: refersTo and reachabilityFence (Java SE 21 API)
- Cleaner (Java SE 21 API)
- WeakHashMap (Java SE 21 API)
- JEP 421: Deprecate Finalization for Removal
- HotSpot GC Tuning Guide, Other Considerations: soft references and SoftRefLRUPolicyMSPerMB (JDK 21)
- JLS §12.6 Finalization of Class Instances, reachability (Java SE 21)
- The java command: -XX:+DisableExplicitGC (JDK 21)