1. Overview
In this article, we’ll have a look at the concept of a Phantom Reference – in the Java language.
2. Phantom References
Phantom references have two major differences from soft and weak references.
We can’t get a referent of a phantom reference. The referent is never accessible directly through the API and this is why we need a reference queue to work with this type of references.
The Garbage Collector adds a phantom reference to a reference queue after the finalize method of its referent is executed. It implies that the instance is still in the memory.
3. Use Cases
There’re two common use-cases they are used for.
The first technique is to determine when an object was removed from the memory which helps to schedule memory-sensitive tasks. For example, we can wait for a large object to be removed before loading another one.
The second practice is to avoid using the finalize method and improve the finalization process.
3.1. Example
Now, let’s implement the second use case to practically figure out how this kind of references works.
First off, we need a subclass of the PhantomReference class to define a method for clearing resources:
public class LargeObjectFinalizer extends PhantomReference<Object> {
public LargeObjectFinalizer(
Object referent, ReferenceQueue<? super Object> q) {
super(referent, q);
}
public void finalizeResources() {
// free resources
System.out.println("clearing ...");
}
}
Now we’re going to write an enhanced fine-grained finalization:
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
List<LargeObjectFinalizer> references = new ArrayList<>();
List<Object> largeObjects = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
Object largeObject = new Object();
largeObjects.add(largeObject);
references.add(new LargeObjectFinalizer(largeObject, referenceQueue));
}
largeObjects = null;
System.gc();
Reference<?> referenceFromQueue;
for (PhantomReference<Object> reference : references) {
System.out.println(reference.isEnqueued());
}
while ((referenceFromQueue = referenceQueue.poll()) != null) {
((LargeObjectFinalizer)referenceFromQueue).finalizeResources();
referenceFromQueue.clear();
}
First, we’re initializing all necessary objects: referenceQueue – to keep track of enqueued references, references – to perform cleaning work afterward, largeObjects – to imitate a large data structure.
Next, we’re creating these objects using the Object and LargeObjectFinalizer classes.
Before we call the Garbage Collector, we manually free up a large piece of data by dereferencing the largeObjects list. Note that we used a shortcut for the Runtime.getRuntime().gc() statement to invoke the Garbage Collector.
It’s important to know that System.gc() isn’t triggering garbage collection immediately – it’s simply a hint for JVM to trigger the process.
The for loop demonstrates how to make sure that all references are enqueued – it will print out true for each reference.
Finally, we used a while loop to poll out the enqueued references and do cleaning work for each of them.
4. Conclusion
In this quick tutorial, we introduced Java’s phantom references.
We learned what these are and how they can be useful in some simple and to-the-point examples.