You are misunderstanding the situation. When map.values().contains(meta)
, or short map.containsValue(meta)
returns false
, it doesn’t imply that meta
has been garbage collected. In fact, you are holding a reference to the object in meta
and even passing that reference to the contains
method which may invoke equals
on it. So how could that object be garbage collected?
The response only tells you that there is no association from one of the map’s keys to that object and since the only key has been garbage collected, that’s the correct answer. Alternatively, you could just have asked map.isEmpty()
to check for the presence of the association.
This is what the WeakHashMap
provides:
Hash table based implementation of the Map
interface, with weak keys. An entry in a WeakHashMap
will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map
implementations.
The removal of the entry is not instantaneous. It relies on enqueuing of the WeakReference
into a ReferenceQueue
, which is then polled internally when you make the next query, like containsValue
or even size()
. E.g. if I change your program to:
Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> ref = new WeakReference<>(p);
p = null;
while(ref.get() != null) System.gc();
System.out.println(map.containsValue(meta)? "Value present": "Value gone");
It occasionally prints “Value present” despite the key Person
instance provenly has been garbage collected at this point. As said above, this is about the map’s internal cleanup, not about the PersonMetadata
instance to which we’re holding a strong reference in meta
anyway.
Making PersonMetadata
eligible to garbage collection is an entirely different thing. As said, the WeakReference
does an internal cleanup whenever we call a method an it. If we don’t, there will be no cleanup and hence, still a strong reference, even if the key has been garbage collected. Consider:
Person p = new Person();
WeakHashMap<Person, PersonMetadata> map = new WeakHashMap<>();
PersonMetadata meta = new PersonMetadata("Geek");
map.put(p, meta);
WeakReference<?> personRef = new WeakReference<>(p);
WeakReference<?> metaRef = new WeakReference<>(meta);
p = null;
meta = null;
while(personRef.get() != null) System.gc();
System.out.println("Person collected");
for(int i = 0; metaRef.get() != null && i < 10; i++) {
System.out.println("PersonMetadata not collected");
System.gc();
Thread.sleep(1000);
}
System.out.println("calling a query method on map");
System.out.println("map.size() == "+map.size());
System.gc();
System.out.println("PersonMetadata "+(metaRef.get()==null? "collected": "not collected"));
Which will print
Person collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
PersonMetadata not collected
calling a query method on map
map.size() == 0
PersonMetadata collected
demonstrating how the WeakHashMap
holds a strong reference to the value of an already collected key until we eventually invoke a method on it, to give it a chance to perform its internal cleanup.
The value finally gets collected when neither, the WeakHashMap
nor our method, hold a reference on it. When we remove the meta = null;
statement, the map still will be empty at the end (after its internal cleanup), but the value won’t be collected.
It’s important to keep in mind that these code examples are for demonstration purposes and touch implementation specific behavior, most notably, that a main
method usually runs unoptimized. Formally, local variables are not required to prevent garbage collection if the referent is otherwise unused, a point which has relevance in practice when methods have been optimized.