☕ Java

WeakHashMap

WeakHashMap is a hash map implementation where the keys are held with weak references. When a key has no strong references elsewhere in the program — when it becomes weakly reachable — the garbage collector can reclaim it, and the corresponding entry is automatically removed from the map. WeakHashMap is used for associating metadata or cached data with objects without preventing those objects from being garbage collected when they are no longer needed.

Weak References and Automatic Entry Removal

Java has four reference strengths: strong, soft, weak, and phantom. A strong reference (the normal kind: Object o = new Object()) prevents the garbage collector from reclaiming the object. A weak reference (java.lang.ref.WeakReference) does not — the GC can collect a weakly-referenced object whenever it determines the object is only weakly reachable (not strongly reachable through any reference chain). WeakHashMap stores each key as a WeakReference rather than a strong reference. When the GC collects a key object (because there are no more strong references to it outside the map), it enqueues the WeakReference in a ReferenceQueue. WeakHashMap polls this queue on each structural operation (put, remove, size, etc.) and removes entries whose keys have been collected. This automatic cleanup is what makes WeakHashMap useful for memory-sensitive caches and object metadata maps. The practical consequence is that entries in a WeakHashMap disappear automatically when their keys become otherwise unreferenced. This is the desired behaviour for use cases like: associating rendering state with a UI widget (when the widget is garbage collected, the rendering state is automatically cleaned up), caching computed values for objects (the cache entry disappears when the object disappears), and storing listener registration data. This behaviour is also a potential source of bugs if misunderstood. If you store a string literal or an interned string as a key, it will never be collected (the JVM holds strong references to interned strings) and the entry persists. If you store a dynamically created String (new String("key")), it may be collected as soon as no other code holds a reference to that specific String object. The same bytes in a different String object will not find the entry because they are different objects — WeakHashMap uses object identity (reference equality) for weak reference keys, not equals().
Java
// ── WeakHashMap — entries removed when key is GC'd: ──────────────────
WeakHashMap<Object, String> wmap = new WeakHashMap<>();

Object key1 = new Object();   // strong reference
Object key2 = new Object();   // strong reference

wmap.put(key1, "value1");
wmap.put(key2, "value2");
System.out.println("Size: " + wmap.size());   // 2

// Remove strong reference to key2:
key2 = null;

// Suggest GC (not guaranteed — illustrative):
System.gc();
Thread.sleep(100);   // give GC time to run

// After GC collects key2's object, the entry is removed:
System.out.println("Size: " + wmap.size());   // 1 or 2 — GC timing uncertain
System.out.println("key1 present: " + wmap.containsKey(key1));  // true
// key2's entry may already be gone

// ── String literals vs new String — important distinction: ────────────
WeakHashMap<String, Integer> strMap = new WeakHashMap<>();

String literal = "hello";         // interned — JVM holds strong reference
strMap.put(literal, 1);
literal = null;                    // release our reference
System.gc();
// "hello" literal STILL in map — JVM's string pool holds it strongly

String dynamic = new String("hello");  // NOT interned — no other strong ref
strMap.put(dynamic, 2);
dynamic = null;                   // release our reference
System.gc();
// dynamic "hello" MAY be GC'd and entry removed

// ── Practical: object metadata without memory leak: ───────────────────
public class ObjectMetadataStore<T> {
    private final WeakHashMap<T, Map<String, Object>> metadata =
        new WeakHashMap<>();

    public void set(T obj, String key, Object value) {
        metadata.computeIfAbsent(obj, k -> new HashMap<>()).put(key, value);
    }

    public Object get(T obj, String key) {
        Map<String, Object> m = metadata.get(obj);
        return m == null ? null : m.get(key);
    }
    // When obj is GC'd, its metadata entry is automatically removed.
    // No manual cleanup needed. No memory leak.
}

Related Topics in Collections Framework

HashMap
HashMap is the most widely used Map implementation in Java. It stores key-value pairs in a hash table, providing average O(1) performance for get, put, remove, and containsKey operations. Keys are stored in no guaranteed order. HashMap permits one null key and multiple null values. It is not thread-safe. Understanding how HashMap uses hash codes, handles collisions, and resizes is foundational knowledge for writing efficient Java code.
LinkedHashMap
LinkedHashMap extends HashMap and maintains a doubly-linked list running through all its entries, preserving the order in which entries were inserted. Iteration over a LinkedHashMap always returns entries in insertion order. Optionally, it can be constructed in access-order mode where entries are ordered by most-recently accessed, making it the perfect foundation for implementing a Least Recently Used (LRU) cache. LinkedHashMap has slightly higher memory overhead than HashMap due to the extra linked list pointers.
TreeMap
TreeMap is a Red-Black tree implementation of the NavigableMap interface. It stores key-value pairs in sorted order — either the natural ordering of keys (requiring them to implement Comparable) or a custom Comparator provided at construction. All basic operations (get, put, remove, containsKey) are O(log n). TreeMap provides rich navigation operations: finding the closest key, extracting sub-maps, and headMap/tailMap views.
Hashtable
Hashtable is a legacy hash table implementation that predates the Collections Framework. Like Vector for lists, Hashtable was Java's original hash map from Java 1.0. It is thread-safe because all its methods are synchronised, but it does not permit null keys or null values. Hashtable has been retrofitted to implement the Map interface but should never be used in new code — HashMap is the non-concurrent replacement and ConcurrentHashMap is the concurrent replacement.