Here we will discuss about how an entry gets garbage collected from WeakhashMap. You may check first how garbage collection works in Java.
WeakHashMap
uses a special class called WeakReference
to refer to the keys. A weak reference is an object that acts like an indirect reference (a reference to an object holding another reference).
It has the interesting property that the garbage collector is allowed to break the reference; i.e. replace the reference, it contains, with null value. And the rule is that a weak reference to an object will be broken when the Garbage Collector notices that the object (key) is no longer reachable via a chain of normal (strong) or soft references.
Let’s take an example as given below…
package com.roytuts.java.weakhashmap.garbage.collection;
public interface Vehicle {
void drive();
}
package com.roytuts.java.weakhashmap.garbage.collection;
public class Car implements Vehicle {
private final String name;
public Car(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void drive() {
System.out.println("I am driving a car " + name);
}
}
Test…
package com.roytuts.java.weakhashmap.garbage.collection;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
public class WeakRefKeyApp {
public static void main(String[] args) throws InterruptedException {
Car bmw = new Car("BMW");
Map<Car, String> carMap = new WeakHashMap<>();
carMap.put(bmw, bmw.getName());
bmw = null;
System.gc();
if (carMap.containsValue("BMW")) {
System.out.println("Value present");
} else {
System.out.println("Value gone");
}
}
}
Output: Value present
When carMap.containsValue("BMW")
method returns true
because the value has not been garbage collected yet. In fact, you are holding a reference to the object in bmw
and even passing that reference to the containsValue()
method which may invoke equals on it.
The removal of the entry is not instantaneous, because internally once containsValue()
is called, expungeStaleEntries()
also gets called. Then garbage collector will hand over the discovered references to another thread which will eventually enqueue the references. The removal of an entry relies on enqueuing of the WeakReference
into a ReferenceQueue
, which is then polled internally when you make a next call, like containsValue()
or size()
on map.
It prints Value present
despite the key bmw
instance has been garbage collected at this point. As said earlier, this is about map’s internal cleanup, not about the Vehicle
instance to which we’re holding a strong reference in bmw
anyway.
Making Vehicle
eligible to garbage collection is an entirely different thing. As said, the WeakReference
does an internal cleanup whenever we call a method on it. If we don’t, there will be no cleanup and hence, still a strong reference, even if the key has been garbage collected.
Let’s take the below example…
package com.roytuts.java.weakhashmap.garbage.collection;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;
public class WeakRefKeyApp {
public static void main(String[] args) throws InterruptedException {
Car bmw = new Car("BMW");
Map<Car, String> carMap = new WeakHashMap<>();
carMap.put(bmw, bmw.getName());
WeakReference<Car> ref = new WeakReference<>(bmw);
bmw = null;
System.gc();
if (carMap.containsValue("BMW")) {
System.out.println("Value present");
} else {
System.out.println("Value gone");
}
System.out.println("ref " + (ref.get() == null ? "garbage collected" : "not garbage collected"));
if (carMap.containsValue("BMW")) {
System.out.println("Value present");
} else {
System.out.println("Value gone");
}
}
}
Output:
Value present
ref garbage collected
Value gone
The value finally gets garbage collected when neither, the WeakHashMap
nor any method, holds a reference on it. When we remove the bmw = null
; statement, the map still will be empty at the end (after its internal cleanup).
The statement System.out.println("ref " + (ref.get() == null ? "garbage collected" : "not garbage collected"));
may print also not garbage collected
. Even you can put System.gc()
inside loop until ref
becomes null
but again this loop may run forever.
This example is for demonstration purpose only and assuming a JVM like HotSpot in its default configuration where System.gc()
is respected and sufficient for cleaning the reference.
Thanks for reading.