Skip to main content

The Mysterious Phantom Reference

Posted by kcpeppe on September 29, 2011 at 1:43 AM PDT

I talk about java.lang.ref.* in my performance tuning course because these things (along with anything that implements finalize) are more expensive to create than normal objects and require at least two rounds of GC before you're completely rid of them. Of the bunch, that includes Reference, WeakReference, SoftReference and two other private reference classes that get mixed up with finalization, PhantomReference has to be the strangest.

 

Though I talk about PhantomReference, I’ve never used to nor could I even think of a situation where I'd even think to use it! Which is, I guess, why I’ve never used it. In fact, I can't remember ever seeing them used any where in the wild. But, as is the case with just about everything,  if you talk to enough people you will eventually run into someone that has tried to used even the most arcane features in Java. True to this point, this week, I finally ran into someone that had a convincing use case for PhantomReference.

 

This person was trying to track down who was leaking JDBC connections. His idea was to wrap the connection in a PhantomReference and when the connection was discarded (and not closed), the garbage collector would put the object into a supplied ReferenceQueue, you’d grab the object from the ReferenceQueue and not only close it, you’d try to sort out who should have closed it. As with just about everything, the devil is in the details and in this case the details are; reference queue doesn’t return the object wrapped in the PhantomReference, it returns the PhantomReference and PhantomReference.get() always returns null. The reason for this is that the wrapped object is half collected and should not be reconnected to anything. I get that but what I don't get it given this condition, why is PhantomReference marketed as alternative mechanisum to finalization. WIth no means to access the wrapped object, (reflection aside) there isn't much one can do to clean things up. Consider the following code.

 

public class Foo {

 

    private String bar;

 

    public Foo(String bar) {

        this.bar = bar;

    }

 

    public String foo() {

        return bar;

    }

}

 

So lets say after the object has been completely dereferenced by the application I want to some how call foo(). Here is some code that I expected to work that would do this with one niggle.

 

        

 

// initialize

ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();

ArrayList< PhantomReference<Foo>> list=new ArrayList<PhantomReference<Foo>>();

 

for ( int i = 0; i < 10; i++) {

    Foo o = new Foo( Integer.toOctalString( i));

    list.add(new PhantomReference<Foo>(o, queue));

}

 

// make sure the garbage collector does it’s magic

System.gc();

 

// lets see what we’ve got

Reference<? extends Foo> referenceFromQueue;

for ( PhantomReference<Foo> reference : list)

    System.out.println(reference.isEnqueued());

 

while ( (referenceFromQueue = queue.poll()) != null) {

    System.out.println(referenceFromQueue.get());

    referenceFromQueue.clear();

}

 

PhantomReference takes an instance of Foo and a ReferenceQueue. Since no handles are kept to Foo, it should immediately be dead. Next, tell the VM to collect as there isn’t enough in heap for it to trigger a collection naturally. The first thing I’m going to ask the PhantomReference is; have you been enqueued. In this case the answer will be true. Next I ask the queue for the reference but as you can see, calling get() always returns null.

 

About the only solution that made sense is to wrap the resources or objects you wanted to interact with in a subclass of PhantomReference.

 

public class FinalizeStuff<Foo> extends PhantomReference<Foo> {

 

    public FinalizeStuff(Foo foo, ReferenceQueue<? super Foo> queue) {

        super(foo, queue);

    }

 

    public void bar() {

        System.out.println("foobar is finalizing resources");

    }

}

 

In this case I’m not going to wrap Foo in the subclass as that would seem to violate the spirit of PhantomReference. Instead I’m going to wrap resources associated with Foo and interact with them. Now I can do this.

 

// initialize

ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();

ArrayList< FinalizeStuff<Foo>> list = new ArrayList<FinalizeStuff<Foo>>();

ArrayList<Foo> foobar = new ArrayList<Foo>();

 

for ( int i = 0; i < 10; i++) {

    Foo o = new Foo( Integer.toOctalString( i));

    foobar.add(o);

    list.add(new FinalizeStuff<Foo>(o, queue));

}

 

// release all references to Foo and make sure the garbage collector does it’s magic

foobar = null;

System.gc();

 

// should be enqueued

Reference<? extends Foo> referenceFromQueue;

for ( PhantomReference<Foo> reference : list) {

    System.out.println(reference.isEnqueued());

}

 

// now we can call bar to do what ever it is we need done

while ( (referenceFromQueue = queue.poll()) != null) {

    ((FinalizeStuff)referenceFromQueue).bar();

    referenceFromQueue.clear();

}

 

This works though in some variations of this implementation main thread was racing against another thread (GC is my best guess). Note the strange need to cast. I could not sort out how to avoid it so if someone wants to comment..... Returning to the use case, the subclass that was created for the JDBC leak captured a stacktrace which was logged when the PhantomReference was pulled from the reference queue.

 

I’m happy for comments from anyone that has actually found a good use for PhantomReference as quite frankly, I still don’t understand why anyone would use it in leu of finalization. While finalization isn’t perfect, it’s not the dog that everyone makes it out to be and it’s far safer to use than PhantomReference is. For example, if anything bad happens during finalization, you’ll only shoot down a helper thread. Furthermore, the only way finalize will not be called is if the VM fails catastrophically or you’ve requested a shutdown but have failed to specify that finalizers (RunTime.runFinalizersOnExit()) should run before doing so. No such guarantees exist for PhantomReference.

 

PS, One point in favor of PhantomReference over finalize is that you could configure your system to optionally wrap objects. But then, you could use an interface with two implementations, one that implemented finalize() and one that doesn't to achieve the same effect. So, I'm still scratching my head over this one.

Related Topics >>