skip to content

What is WeakHashMap, what makes its keys garbage-collectible, and what is a common use for it?

level: seniorimportance: should knowfreq 50%

answer

  1. Keys via WeakReference -> GC can reclaim them
  2. Values are STRONG (value->key ref pins the entry)
  3. Cleanup is lazy via ReferenceQueue on map access
  4. Use: metadata/caches that must not keep objects alive
  5. Non-deterministic timing; not thread-safe

basics

~20 s

WeakHashMap holds its keys with weak references, so once nothing else points to a key, the garbage collector can remove it and its entry disappears automatically. It's handy for caches that should not keep objects alive.

solid answer

~50 s

WeakHashMap is a Map whose keys are held via WeakReference. A weak reference does not stop the garbage collector from reclaiming an object: if the only thing referencing a key is the WeakHashMap itself, the GC can collect that key, and the map then drops the corresponding entry automatically (lazily, as you touch the map, via a ReferenceQueue). This makes it ideal for auxiliary metadata or caches that must not keep their keys alive — e.g. associating extra data with objects without causing memory leaks. The classic gotcha is keying on values you also hold strong references to, or keying on something with no outside strong reference (like a String literal that's interned, or a freshly created key you don't retain) — entries may vanish unexpectedly, or never, depending on what holds the key. Note: only keys are weak; values are strong, so a value that references its own key can pin the entry (preventing collection). It is not thread-safe.

code

java · 12 lines
java
WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "metadata");
System.out.println(map.size());   // 1

key = null;                        // remove the only strong reference
System.gc();                       // hint the collector
// timing is non-deterministic; after GC + a map access:
System.out.println(map.size());   // may print 0

// LEAK TRAP: value strongly references its own key ->
// the key is never weakly-reachable, entry never collected.

go deeper

for a junior

Knows WeakHashMap keys can be garbage-collected when nothing else references them, useful to avoid leaks.

for a middle

Explains weak vs strong references, that the GC reclaims weakly-held keys and the map drops the entry, and a basic cache/metadata use case.

for a senior

Explains the ReferenceQueue/lazy expunge mechanism, that only keys are weak (value->key pinning leak), non-deterministic timing, and the interned-string/boxed-Integer pitfalls.

for a principal

Reasons about WeakHashMap vs soft references vs a real cache, framework use (ThreadLocalMap-style), memory-leak diagnosis, GC interaction/visibility, and why deterministic resource cleanup needs a different tool.

## Reference strength in Java The garbage collector (GC) reclaims objects that are no longer **reachable**. But not all references are equal: - **Strong reference** (`Object o = ...;`): the normal kind. As long as a strong reference exists, the object is reachable and **cannot** be collected. - **Weak reference** (`WeakReference<T>`): a special wrapper. If the *only* references to an object are weak, the GC is **free to reclaim it**. After collection, the weak reference's `get()` returns `null`. (There are also soft and phantom references, but weak is what matters here.) ## What WeakHashMap does `WeakHashMap` is a hash map that stores each **key inside a `WeakReference`** (specifically a subclass tied to a `ReferenceQueue`). The **values are held strongly**. The effect: > If no strong reference to a key exists anywhere else in your program, the GC may collect that key. The map then **automatically removes** the now-dead entry. This cleanup is **lazy**: the map doesn't get a callback the instant the GC runs. Instead, the GC enqueues the cleared weak references onto a `ReferenceQueue`; the next time you call a method like `size()`, `get()`, or iterate, the map drains that queue (its `expungeStaleEntries()` step) and removes the dead entries. So `size()` can shrink over time even though you never called `remove()`. ``` WeakHashMap<Object,String> m = new WeakHashMap<>(); Object key = new Object(); m.put(key, "meta"); // entry present key = null; // drop the only strong ref System.gc(); // GC may now reclaim the key // after some map access, m.size() can become 0 ``` ## Why this is useful — canonical use cases 1. **Attaching metadata to objects without owning their lifecycle.** You want to associate extra info with objects you don't control, but you must **not** keep them alive. WeakHashMap lets the entry die when the object dies — no memory leak. This is exactly how `ThreadLocal` internals and many frameworks track per-object state. 2. **Caches that should evict naturally.** A cache keyed by objects that the rest of the app still uses; when the app stops using an object, its cache entry can go too. (For value-driven caches, you usually want a real cache library or soft references instead.) 3. **Canonicalizing maps / listener registries** that shouldn't prevent the registered objects from being collected. ## The big gotchas - **Only keys are weak; values are strong.** If a *value* (or anything the value references) holds a strong reference back to its **key**, the key can never become weakly-reachable, so the entry is **never** collected — a silent leak. Fix: wrap such values in a `WeakReference` too, or restructure. - **Identity of the key reference matters.** If the only reference to the key is the one you used in `put` and you drop it, the entry can vanish on the next GC — sometimes surprisingly fast. Conversely, **interned strings / literals / cached boxed values** are kept alive by the JVM, so entries keyed on them may **never** disappear. - **Non-deterministic timing.** You cannot predict *when* entries disappear; it depends on GC. Don't rely on WeakHashMap for correctness that needs deterministic removal. - **Not thread-safe.** Wrap with `Collections.synchronizedMap` if shared, or use a concurrent alternative. ## Mental model A normal `HashMap` is a strong owner: it keeps every key alive forever (until you `remove`). `WeakHashMap` is a *non-owning observer*: it remembers a key only as long as someone *else* still cares about it, then quietly forgets.

  • Why might a WeakHashMap entry never get collected even after you drop your reference to the key?
    Because something still holds a strong reference to the key. A common cause is the entry's own value (held strongly) referencing the key directly or transitively, pinning it. Interned strings, literals, and cached boxed integers are also kept alive by the JVM.
  • When exactly are dead entries removed from a WeakHashMap?
    Lazily. The GC enqueues cleared key references onto a ReferenceQueue; the map drains that queue and expunges stale entries during subsequent operations (size, get, put, iteration). There is no immediate callback, so removal timing is non-deterministic.

WeakHashMap is a coat-check that only keeps your ticket while you still hold the coat. The moment you let go of the coat (no strong reference), the attendant is allowed to throw out your ticket too.

saying these in an interview costs you the question

  • Saying values are also weak — only keys are weak; values are strong.
  • Relying on deterministic/immediate removal of entries.
  • Keying on a String literal or interned string and expecting it to be collected.
  • Using it as a thread-safe cache without synchronization.
  • Not realizing a value->key strong reference pins the entry (leak).

context