How Garbage Collection works in WeakHashMap

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.

Leave a Reply

Your email address will not be published. Required fields are marked *