skip to content

How does WeakHashMap work, what is it useful for, and what are its common pitfalls?

level: seniorimportance: should knowfreq 50%

answer

  1. Keys held weakly; values held strongly
  2. Entry auto-removed when key otherwise-unreachable
  3. Purge is lazy via an internal ReferenceQueue on next access
  4. Value referencing its key = leak that never shrinks
  5. Not a cache, not thread-safe, size is GC-dependent

basics

~20 s

WeakHashMap is a map that holds its keys with weak references. When a key is no longer used anywhere else, the garbage collector reclaims it and the map automatically removes that entry, so the map can't keep old keys alive.

solid answer

~50 s

WeakHashMap stores each key in a WeakReference, so a key is kept alive only by references outside the map. When a key becomes otherwise-unreachable, the GC reclaims it, the cleared reference is enqueued, and the map removes that entry (lazily, when you next touch the map). This makes it ideal for metadata side-tables and canonicalizing maps — associating data with objects you don't own without leaking them. The main pitfalls: it's the keys that are weak, not the values, so a value that strongly references its own key (directly or transitively) pins the key and prevents removal — a classic self-defeating leak. Entry removal is non-deterministic (tied to GC), so size() and iteration can shift unexpectedly, and it's not thread-safe. Also, if you keep a strong reference to a key elsewhere, the entry never disappears. For value-keyed weakness or stronger guarantees you'd reach for different tools (e.g. an explicit cache, or identity-based maps depending on equality needs).

code

java · 14 lines
java
import java.util.Map;
import java.util.WeakHashMap;

Map<Object, String> sideTable = new WeakHashMap<>();
Object key = new Object();
sideTable.put(key, "metadata");

System.out.println(sideTable.size()); // 1

key = null;       // drop the only strong reference to the key
System.gc();      // suggest a collection; GC may now reclaim the key

// Removal is lazy: touching the map drains the internal ReferenceQueue
System.out.println(sideTable.size()); // likely 0 after the key is reclaimed

go deeper

for a junior

Knows WeakHashMap removes entries when keys are no longer used, and that it's keyed weakly.

for a middle

Explains that keys are weak, values strong; names side-table/canonicalizing uses; knows it isn't a cache.

for a senior

Explains the ReferenceQueue-driven lazy purge, the value-pins-key leak, non-deterministic size, thread-safety, and when to choose alternatives.

for a principal

Weighs WeakHashMap against bounded caches and weak-value libraries, reasons about leak audits in long-lived processes, and sets guidance on when identity-keyed weak maps are appropriate at system scale.

## Recap: weak references A `WeakReference<T>` points at an object (the referent) without keeping it alive. Once nothing else *strongly* references the object, the GC reclaims it at the next collection and the weak reference's `get()` returns `null`. `WeakHashMap` is built on this. ## What WeakHashMap does `java.util.WeakHashMap<K,V>` is a `Map` implementation where each **key** is held through a weak reference (internally, each entry's key is a `WeakReference` registered with a `ReferenceQueue`). The consequence: > An entry remains in the map only as long as **something outside the map** strongly reaches its key. Once the key is otherwise-unreachable, the GC reclaims it, and the map drops the entry on its own. Mechanically, when the GC clears a key's weak reference it **enqueues** that reference on the map's internal `ReferenceQueue`. The map doesn't get a callback; instead it **lazily** drains that queue and purges stale entries the next time you call a method like `get`, `put`, or `size`. So removal is deferred until the map is next touched. ```java Map<Widget, Metadata> sideTable = new WeakHashMap<>(); Widget w = new Widget(); sideTable.put(w, new Metadata()); // ... while 'w' (or another strong ref to it) is live, the entry stays w = null; // drop the only strong reference to the key System.gc(); // the entry for that widget will be removed automatically (lazily, on next access) ``` ## What it's good for - **Metadata side-tables.** You want to attach information to objects you don't own (can't add a field to). A `WeakHashMap` keyed by those objects holds the metadata, and entries vanish when the objects are no longer used — no manual cleanup, no leak. - **Canonicalizing maps / interning.** Map a representative object to a canonical instance without keeping unused representatives alive. - **Listener / registration tables** where you want a registration to disappear when the registrant is collected. ## Pitfall 1: keys are weak, values are not — and values can pin keys The single most important gotcha: **only the keys are held weakly; the values are held strongly by the map.** If a **value** strongly references its own **key** (directly, or through any object graph), then as long as the entry exists the value keeps the key strongly reachable — so the key never becomes weakly reachable, the entry never gets removed, and you have a permanent leak. This is the classic 'WeakHashMap that never shrinks' bug. Fixes: don't let the value reference the key, or wrap the value's back-reference in a `WeakReference` too. ## Pitfall 2: non-deterministic, GC-driven removal Entries disappear only after a GC clears the key *and* the map is subsequently touched. So `size()`, iteration, and `containsKey` can return different results across calls without you modifying the map, purely because of GC. Don't rely on stable size or on an entry persisting across a GC if its only strong reference was dropped. ## Pitfall 3: identity vs equality of keys `WeakHashMap` uses the keys' normal `equals`/`hashCode`. But because keys are tracked by reachability of the *object*, it's most natural and predictable with keys whose identity matters. Using value-like keys (e.g. interned strings, boxed constants) can behave surprisingly because those keys may be strongly referenced elsewhere (string literals live in the pool) and so never get collected. ## Pitfall 4: not thread-safe Like `HashMap`, `WeakHashMap` is unsynchronized. Concurrent structural modification (including the automatic purges) needs external synchronization, e.g. `Collections.synchronizedMap(new WeakHashMap<>())`, or a different structure entirely. ## When NOT to use it - You want a **cache** (keep values while memory allows) — that's a soft/bounded cache, not a weak-keyed map. - You need **weak values** rather than weak keys — `WeakHashMap` can't do that; reach for a library (e.g. Guava's `MapMaker`/`CacheBuilder` with weak values, or Caffeine). - You need deterministic eviction or a guaranteed size — use an explicit, bounded structure. ## Summary WeakHashMap = a `Map` whose entries quietly disappear when their keys go out of use. Perfect for leak-free side-tables; dangerous if a value pins its key, if you assume a stable size, or if you mistake it for a memory-sensitive cache.

  • Why might a WeakHashMap never shrink even after keys go out of scope?
    Because something is still strongly reaching the keys. The classic cause is a value (held strongly by the map) that references its own key, so the key stays strongly reachable and the entry is never purged. Any external strong reference to a key has the same effect.
  • How does WeakHashMap actually remove stale entries — is there a callback?
    No callback. Each key's WeakReference is registered with an internal ReferenceQueue. When the GC clears a key, it enqueues the reference; the map lazily drains that queue and purges stale entries the next time one of its methods (get/put/size, etc.) runs.

saying these in an interview costs you the question

  • Saying WeakHashMap holds values weakly (it's the keys)
  • Using it as a memory-sensitive cache (that's soft references)
  • Assuming entries vanish instantly when the key is dropped — removal is lazy/GC-driven
  • Ignoring that a value referencing its key defeats the whole mechanism
  • Treating it as thread-safe

context