⚙️ JVM Internals & Memory · Advanced

Reference types in Java

Strong, soft, weak, phantom references; WeakHashMap; Cleaner.

🧩 The mysteryYou build a cache that lets the GC throw entries away when memory gets tight, without writing any eviction code. Java has a built-in tool for exactly that.

Ropes, rubber bands, threads

Normal references are strong: they keep objects alive. java.lang.ref adds weaker kinds. A SoftReference is cleared only when memory runs low. A WeakReference is cleared at the next GC once the object is only weakly reachable. A PhantomReference only tells you the object is gone.

🔮 Predict it

Ask the references

The object is still strongly referenced by key. What does this print?

String key = new String("k");
var weak = new WeakReference<>(key);
var ph = new PhantomReference<>(
        key, new ReferenceQueue<>());
System.out.println(weak.get());
System.out.println(ph.get());
  1. k null
  2. null null
  3. k k
Show the answer

key still holds the object strongly, so the weak reference returns it. A phantom reference's get() always returns null, by design.

Why phantoms return null

If get() could return the object, cleanup code could resurrect it after it was declared dead. So a PhantomReference is registered with a ReferenceQueue and simply gets enqueued after the object dies. **Cleaner** is built on this.

WeakHashMap

A WeakHashMap holds its keys weakly and its values strongly. Once a key is no longer strongly reachable, the GC clears it and the map drops the whole entry, even if the value is still used elsewhere.

⚠️ The trap

The value that holds its key

This entry never disappears. The map holds m strongly, and m holds the key strongly, so the key stays reachable through the map itself and is never cleared.

Map<Key, Meta> cache = new WeakHashMap<>();
Key k = new Key("img-42");
Meta m = new Meta(k); // Meta stores its key
cache.put(k, m);
k = null; // entry stays forever

Releasing a native handle

✗ finalize()
protected void finalize() {
    handle.close();
}

Deprecated for removal; slow and unpredictable timing.

✓ AutoCloseable + Cleaner
try (var f = new NativeFile(p)) {
    f.read();
} // close() runs here

Explicit close via try-with-resources; register a Cleaner as a safety net.

💼 In the real world

Where each one fits

Soft references suit memory-sensitive caches. WeakHashMap suits metadata attached to objects you don't control, so it disappears with them. Cleaner backs up native resources like file handles or off-heap buffers, but close() should still be the main path.

Key takeaways

  1. Strong > soft > weak > phantom
  2. WeakHashMap holds its keys weakly, its values strongly
  3. PhantomReference.get() always returns null
  4. Use Cleaner or try-with-resources, not finalize()

💡 A strong reference is a rope, a soft one a rubber band that snaps under stress, a weak one a thread, and a phantom one just a note saying the thing is gone.

🤯 Did you know?

finalize() was deprecated in Java 9, the same release that introduced java.lang.ref.Cleaner. JEP 421 in Java 18 then marked finalization for removal.

Practice questions

What does this print?

Object o = new Object();
var weak = new WeakReference<>(o);
var ph = new PhantomReference<>(
        o, new ReferenceQueue<>());
System.out.println(weak.get() == o);
System.out.println(ph.get());
  1. true null
  2. null null
  3. false null
  4. true true
Check your answer

true null. o is still strongly reachable, so the weak reference still returns it. A phantom reference's get() always returns null by design.

A class closes a native file handle in finalize(). What's the modern replacement?

  1. Call System.gc() more often
  2. Implement AutoCloseable for try-with-resources, with a Cleaner as a safety net
  3. Keep finalize() but make it synchronized
  4. Store the handle in a static field
Check your answer

Implement AutoCloseable for try-with-resources, with a Cleaner as a safety net. Finalization is deprecated for removal: it is slow and unpredictable. Explicit close() via try-with-resources is primary, and Cleaner is a backup if someone forgets.

Weak references help objects go away. But what if your code keeps a strong grip by accident? Next: memory leaks in a garbage-collected language.