Skip to Content
Course content

199: Weak References and WeakHashMap

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen a lot of developers run into a specific kind of memory leak when building caches. They want to associate some extra data with an object—maybe a "metadata" or "state" object—but they don't want the cache itself to be the reason that object stays in memory forever. If the rest of the application is done with the object, the cache should just let it go.

Creating a "Leaky" User Metadata Cache

Let's imagine we're building a system that tracks some expensive analysis data for User objects. Since calculating this analysis is slow, we want to cache it. My first instinct might be to use a standard HashMap. Here's how I'd start:

public class UserAnalysisCache {
    private final Map<User, AnalysisData> cache = new HashMap<>();

    public void putAnalysis(User user, AnalysisData data) {
        cache.put(user, data);
    }

    public AnalysisData getAnalysis(User user) {
        return cache.get(user);
    }
}

The problem here is that HashMap holds strong references to its keys. Even if the rest of your application drops every single reference to a User object, that user will stay in memory because the cache map is still holding onto it. In a long-running server, this is a classic memory leak.

The "Wrong" Attempt at a Fix

Now, you might think, "I'll just wrap the key in a WeakReference!" I did this early in my career, and it's a great example of how things can go wrong if you don't understand how the GC interacts with Map entries. I tried this:

// DON'T DO THIS
private final Map<WeakReference<User>, AnalysisData> cache = new HashMap<>();

public void putAnalysis(User user, AnalysisData data) {
    cache.put(new WeakReference<>(user), data);
}

I quickly realized this is actually worse. Why? Because the HashMap now holds a strong reference to the WeakReference object itself. While the User inside that wrapper can be garbage collected, the wrapper object and the AnalysisData value stay in the map forever. You end up with a map full of empty WeakReference keys and orphaned values. It's still a leak, just a slightly more sophisticated one.

Letting WeakHashMap Handle the Heavy Lifting

This is exactly why Java gives us WeakHashMap. It's designed specifically for this use case. In a WeakHashMap, the keys are stored as weak references internally. When the only remaining reference to a key is the one held by the map, the garbage collector is free to reclaim that key. Once the key is reclaimed, the map automatically removes the entire entry.

Let's rewrite our cache properly:

import java.util.Map;
import java.util.WeakHashMap;

public class UserAnalysisCache {
    // The keys (User objects) are now weakly referenced
    private final Map<User, AnalysisData> cache = new WeakHashMap<>();

    public void putAnalysis(User user, AnalysisData data) {
        cache.put(user, data);
    }

    public AnalysisData getAnalysis(User user) {
        return cache.get(user);
    }
    
    public int getCacheSize() {
        return cache.size();
    }
}

Now, if the User object is no longer used anywhere else in your code, the GC will wipe it out, and the WeakHashMap will eventually prune that entry. One thing to keep in mind: WeakHashMap is not thread-safe. If you're using this in a multi-threaded environment, you'll need to wrap it using Collections.synchronizedMap().

Also, a word of caution: be careful not to accidentally create a strong reference to the key inside the value. If AnalysisData contains a reference back to the User, you've created a strong reference chain that prevents the key from ever being collected, defeating the whole purpose of the WeakHashMap.




📋 Practical Task

Build a Leak-Proof Image Metadata Store

You are building a photo editing application. You have Image objects (which are very large) and you want to store ImageMetadata (which is small, containing things like GPS coordinates and camera settings) in a cache. You must ensure that when the application stops using an Image, the corresponding ImageMetadata is automatically cleared from the cache to save memory.

Requirements:

  • Create a class ImageMetadataStore.
  • Use a WeakHashMap to associate Image keys with ImageMetadata values.
  • Implement a method void store(Image img, ImageMetadata meta).
  • Implement a method ImageMetadata retrieve(Image img).
  • Create a simple main method to demonstrate the behavior:
    • Create an Image object.
    • Store metadata for it.
    • Set the Image reference to null.
    • Call System.gc() (while not guaranteed, it usually triggers the cleanup for this demonstration).
    • Print the size of the map to verify the entry was removed.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.