Course contents

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.

PremiumOften askedLesson15 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: the library desk
  2. Under the hood: the four types in code
  3. ReferenceQueue and WeakHashMap
  4. How an ordinary object becomes eligible for GC
  5. Advanced: Cleaner instead of finalize()
  6. Advanced: why soft-reference caches backfire
  7. How to say it in the interview

🔗 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)

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, WeakReference and PhantomReference with a ReferenceQueue, and know why PhantomReference.get() always returns null
  • Replace finalize() with **java.lang.ref.Cleaner** plus try-with-resources
  • Explain how WeakHashMap drops 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.

Fig. 1

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

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-3 only weakly reachable.
  • Phantom returns null even before the GC runs. Its only output is the enqueue, which arrived on queue.

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)