☕ 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
Collections Overview
The Java Collections Framework (JCF) is a unified architecture for representing and manipulating groups of objects. It provides a set of interfaces that define the operations a collection must support, a set of abstract classes that provide partial implementations, and a set of concrete implementations optimised for different use cases. Every Java developer uses collections daily — lists for sequences, sets for uniqueness, maps for key-value pairs, and queues for ordering — and choosing the right implementation for the right use case is one of the most fundamental practical skills in Java.
Iterable
Iterable<E> is the root interface of the Java Collections hierarchy. Any class that implements Iterable can be used in a for-each loop. It declares a single abstract method: iterator(), which returns an Iterator<E> that the for-each loop uses to traverse the elements. Implementing Iterable is all that is required to make a custom data structure work with Java's enhanced for loop, the Stream API, and any method that accepts an Iterable.
Collection Interface
Collection<E> is the root interface of the main collection hierarchy, extending Iterable<E>. It defines the common operations that all collection types must support: adding elements, removing elements, checking containment, querying size, clearing, converting to an array, and bulk operations. List, Set, and Queue all extend Collection. Map does not extend Collection because a map operates on key-value pairs rather than individual elements.
ArrayList
ArrayList is a resizable-array implementation of the List interface. It is the most commonly used collection in Java, providing dynamic sizing on top of a standard array. Elements are stored in contiguous memory, enabling O(1) random access by index. When the internal array is full, ArrayList automatically allocates a larger array and copies all elements. ArrayList is not thread-safe and preserves insertion order, allowing duplicates.