views:

107

answers:

3

Say there are two objects, A and B, and there is a pointer A.x --> B, and we create, say, WeakReferences to both A and B, with an associated ReferenceQueue.

Assume that both A and B become unreachable. Intuitively B cannot be considered unreachable before A is. In such a case, do we somehow get a guarantee that the respective references will be enqueued in the intuitive (topological when there are no cycles) order in the ReferenceQueue? I.e. ref(A) before ref(B). I don't know - what if the GC marked a bunch of objects as unreachable, and then enqueued them in no particular order?

I was reviewing Finalizer.java of guava, seeing this snippet:

private void cleanUp(Reference<?> reference) throws ShutDown {
  ...
  if (reference == frqReference) {
    /*
     * The client no longer has a reference to the
     * FinalizableReferenceQueue. We can stop.
     */
    throw new ShutDown();
  }

frqReference is a PhantomReference to the used ReferenceQueue, so if this is GC'ed, no Finalizable{Weak, Soft, Phantom}References can be alive, since they reference the queue. So they have to be GC'ed before the queue itself can be GC'ed - but still, do we get the guarantee that these references will be enqueued to the ReferenceQueue at the order they get "garbage collected" (as if they get GC'ed one by one)? The code implies that there is some kind of guarantee, otherwise unprocessed references could theoretically remain in the queue.

Thanks

+2  A: 

I'm pretty sure that the answer is no.

The JVM spec says this about finalizer methods:

The Java virtual machine imposes no ordering on finalize method calls. Finalizers may be called in any order or even concurrently. (JVM spec 2.17.7)

From this I infer that there are no guarantees that references are queued in topological order.

Stephen C
+1  A: 

I think there is no such guarantee. The GC itself does not have a complete and immediate view of the RAM (it cannot, since the GC runs on the CPU which can only look at a few bytes at a time). In your example, assuming a basic "mark & sweep" GC, chances are that A and B will be declared unreachable in the same mark phase, and swept together in no particular order. Maintaining topological order would probably be expensive.

As for Finalizer, it seems that it is meant to be used through a FinalizableReferenceQueue instance only, which does some classloader-related magic. The Finalizer uses its own facilities to detect when the FinalizableReferenceQueue from which it functionally depends becomes itself unreachable; this is the point when the thread which runs Finalizer knows that it should exit. From what I understand, if the application lets the GC reclaim the FRQ, then the finalizer thread will exit, and any references enqueued "after" the FRQ reference will not be processed. This depends on topological order or lack thereof, but I cannot decide whether this is a problem or not. I think that the application is not supposed to drop its FRQ as long as processing of reclaimed referenced objects is important.

Thomas Pornin
Maintaining topological order would be impossible: what if `A` references `B` and `B` references `A`? Which one s first in topological order?
Joachim Sauer
We can only talk about topo sort in acyclic graphs, please look my original post.
Dimitris Andreou
Thomas, the (phantom) reference is to a ReferenceQueue (which is in FRQ), not FRQ itself. Apart from that, you seem to agree that this code could end up leaving unprocessed references. Hmm. I'll fwd this to Bob Lee, I suspect there is some other trickery going on.
Dimitris Andreou
+2  A: 
Bob Lee
Ah, thanks Bob. :) It's RTFM I suppose. Amazingly, FinalizableReferenceQueue has two sentences of class javadocs, and the second happens to be devoted precisely to this matter. (http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/base/FinalizableReferenceQueue.html)
Dimitris Andreou