Strong, soft, weak and phantom

JVM · lesson 20 of 34 · 4 min read

Four ways to hold an object, from never collected to a signal that it has already gone.

Open this lesson in the learning hub

Key points

  • An ordinary field is a strong reference. While one is reachable from a GC root the object simply cannot be collected.
  • A SoftReference is cleared only when memory is running short. The VM must clear every one before it throws an OOME.
  • A WeakReference is cleared at the next collection that finds nothing else pointing at the object.
  • A PhantomReference never hands the object back. It exists purely to tell you the object has gone.
  • A ReferenceQueue reports which references were cleared. That is how WeakHashMap and Cleaner tidy.
  • Use Cleaner, never finalize(). Finalizers are deprecated for removal and can resurrect the object they clean.

Example

import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        byte[] strong = new byte[1024];

        WeakReference<byte[]> weak = new WeakReference<>(strong, queue);
        SoftReference<byte[]> soft = new SoftReference<>(new byte[1024]);

        System.out.println("while a strong reference exists : weak=" + state(weak.get())
                + "  soft=" + state(soft.get()));

        strong = null;                                  // the only strong reference is gone
        for (int i = 0; i < 10 && weak.get() != null; i++) { System.gc(); Thread.sleep(20); }

        Reference<?> dead = null;
        for (int i = 0; i < 20 && dead == null; i++) { dead = queue.poll(); Thread.sleep(10); }

        System.out.println("after dropping it and running a few collections:");
        System.out.println("  weak  -> " + state(weak.get()) + "   cleared: nothing else pointed at it");
        System.out.println("  soft  -> " + state(soft.get()) + "       kept: the heap is nowhere near full");
        System.out.println("  the cleared weak reference was enqueued: " + (dead != null));
        System.out.println();

        Cleaner cleaner = Cleaner.create();             // runs on its own daemon thread
        Object handle = new Object();
        cleaner.register(handle, () ->
                System.out.println("  cleaner ran - this is where you release a native handle"));
        handle = null;

        System.out.println("Cleaner is the replacement for finalize():");
        for (int i = 0; i < 20; i++) { System.gc(); Thread.sleep(20); }
        System.out.println("  (the action never sees the object, so it cannot resurrect it)");
    }

    static String state(Object o) { return o == null ? "collected" : "alive    "; }
}

Strong keeps it, soft caches it, weak forgets it, phantom just tells you.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.