Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

> CHECK OUT THE COURSE

1. Introduction

When we program in Java, we often use hard references, usually without even thinking about it — and for a good reason, because they're the best option for most circumstances. However, sometimes we need more control over the time when objects are available for clearing by the garbage collector.

In this article, we'll explore the differences between hard and various non-hard reference types and when we can use them.

2. Hard References

A hard (or strong) reference is the default type of reference, and most of the time, we may not even think about when and how referenced objects are garbage collected. The object can't be garbage collected if it's reachable through any strong reference. Let's say we create an ArrayList object and assign it to the list variable:

List<String> list = new ArrayList<>;

The garbage collector can't collect this list because we hold a strong reference to it in the list variable. But if we then nullify the variable:

list = null;

Now and only now, the ArrayList object can be collected because nothing holds a reference to it.

3. Beyond Hard References

There's a good reason why hard references are the default. They let the garbage collector work as intended, so we don't have to worry about managing memory allocation. Despite that, there are situations in which we want to collect objects and deallocate memory even if we still hold a reference to these objects.

4. Soft References

A soft reference tells the garbage collector that a referenced object can be collected at the collector's discretion. The object can stay in the memory for some time until the collector decides that he needs to collect it. That'll happen, especially when JVM is at risk of running out of memory. All soft references to objects reachable only by soft reference should be cleared out before the OutOfMemoryError exception is thrown.

We can use a soft reference easily by wrapping it around our object:

SoftReference<List<String>> listReference = new SoftReference<List<String>>(new ArrayList<String>());

If we want to retrieve the referent, we can use the get method. Because the object could be already cleared, we need to check for it:

List<String> list = listReference.get();
if (list == null) {
    // object was already cleared
}

4.1. Use Case

Soft references can be used to make our code more resilient to errors connected to insufficient memory. For example, we could create a memory-sensitive cache that automatically evicts objects when memory is scarce. We wouldn't need to manage the memory manually, as the garbage collector would do it for us.

5. Weak References

Objects referenced only by weak references aren't prevented from being collected. From the perspective of garbage collection, they could not exist at all. If a weakly referenced object should be protected from being cleared, it should also be referenced by some hard reference.

5.1. Use Case

Weak references are most often used to create canonicalizing mappings. These are mappings that map only objects that can be reached. A great example is WeakHashMap, which works like normal HashMap, but its keys are weakly referenced, and they are automatically removed when the referent is cleared.

Using WeakHashMap, we can create a short-living cache that clears objects that are no longer used by other parts of the code. If we used a normal HashMap, the mere existence of the key in the map would prohibit it from being cleared by the garbage collector.

6. Phantom References

Similarly to weak references, phantom references don't prohibit the garbage collector from enqueueing objects for being cleared. The difference is phantom references must be manually polled from the reference queue before they can be finalized. That means we can decide what we want to do before they are cleared.

6.1. Use Case

Phantom references are great if we need to implement some finalization logic, and they're considerably more reliable and flexible than the finalize method. Let's write a simple method that will go through the reference queue and perform cleanup for all references:

private static void clearReferences(ReferenceQueue queue) {
    while (true) {
        Reference reference = queue.poll();
        if (reference == null) {
            break; // no references to clear
        }
        cleanup(reference);
    }
}

Phantom references don't allow us to retrieve their referent using the get method. Because of that, it's a common practice to extend the PhantomReference class with our own that contains information important for the cleanup logic.

Other important use cases for phantom references are debugging and memory leak detection. Even if we don't need to perform any finalizing operations, we can use phantom references to observe what objects are being deallocated and when.

7. Conclusion

In this article, we explored hard and different types of non-hard references and their use cases. We learned that soft references could be used for memory protection, weak references for canonicalizing mappings, and phantom references for fine-grained finalization.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
Comments are closed on this article!