Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
199: Weak References and WeakHashMap
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
WeakHashMapto associateImagekeys withImageMetadatavalues. - Implement a method
void store(Image img, ImageMetadata meta). - Implement a method
ImageMetadata retrieve(Image img). - Create a simple
mainmethod to demonstrate the behavior:- Create an
Imageobject. - Store metadata for it.
- Set the
Imagereference tonull. - 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.
- Create an
There are no comments for now.