I have a PR, an object O to which the PR points, and an RQ set up for PR. I have a thread which keeps on polling the RQ, and at the first reference it finds in RQ, the thread prints the time at which it found it, and exits.
Things work fine, but the moment O has a finalize (no matter how trivial), the thread no longer finds a reference in RQ and keeps running indefinitely.
Question: why does it happen this way? I'm using Sun JDK 1.6.
Here's the code:
good case
public class MyGCPhantom
{
public static void main(String[] args) throws InterruptedException
{
GCPhantomObject p = new GCPhantomObject();
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference<GCPhantomObject> pr = new PhantomReference<GCPhantomObject>(p, phantomQueue);
new GCPhantomThread(phantomQueue, "Phantom").start();
p = null;
System.gc();
}
}
class GCPhantomObject
{
@Override
protected void finalize()
{
//System.out.println("GCPhantom finalized " + System.currentTimeMillis());
}
}
class GCPhantomThread extends Thread
{
private ReferenceQueue referenceQueue;
private String name;
GCPhantomThread(ReferenceQueue referenceQueue, String name)
{
this.referenceQueue = referenceQueue;
this.name = name;
}
@Override
public void run()
{
while(referenceQueue.poll() == null);
System.out.println(name + " found at " + System.currentTimeMillis());
}
}
bad case
Just uncomment the SOP in finalize()
of GCPhantomObject
.
Your analysis is somewhat off. In both the good case and the bad case your object implements finalize
. In the good case, it implements it trivially; in the bad case, non-trivially. Therefore the obvious problem is in the difference between a trivial and non-trivial implementation of finalize
.
I see no reason why JVM would be forced by specification to enqueue your refs. You do a single GC run and then just keep waiting for something to happen. It is known that any nontrivial finalizer may resurrect the object, therefore there may be more GC cycles needed before it is enqueued. I suggest adding more GC calls.
Also note that your decision to use poll
instead of remove
is not advised. You should use a blocking call to prevent busy-polling.
For reference, these are the relevant definitions from the documentation:
If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.
An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.
A finalized object has had its finalizer automatically invoked.
The Phantom Reference won't appear in the ReferenceQueue until after the object have been finalized. You are doing a busy loop so it is problematic. Note that finalization requires at least two gcs.
I just tried the code posted here on my system and it does not work even after two System.gc() calls. It does not terminate even if place System.gc() calls in the while loop of GCPhantomThread class here.
It seems to me that the issue here is that the object you are creating never gets placed in the ReferenceQueue because it is not even phantom-reachable when the GCPhantomThread is running. The PhantomReference to the object in the main() method falls out of scope and so when you have the GCPhantomThread running, the object is not even phantom-reachable. According to the docs, for the phantom reference to be enqueued, finalization and phantom-reachability are necessary.
It works when I pass the phantom reference to GCPhantomThread. On my machine this code always terminates:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MyGCPhantom {
public static void main(String[] args) throws InterruptedException {
GCPhantomObject p = new GCPhantomObject();
ReferenceQueue phantomQueue = new ReferenceQueue();
PhantomReference pr = new PhantomReference(p, phantomQueue);
new GCPhantomThread(pr, phantomQueue, "Phantom").start();
p = null;
pr = null;
System.gc();
System.out.println("main thread done ...");
}
}
class GCPhantomObject {
@Override
protected void finalize() {
System.out.println("GCPhantom finalized at " + System.nanoTime());
}
}
class GCPhantomThread extends Thread {
private ReferenceQueue referenceQueue;
private String name;
private PhantomReference pr;
GCPhantomThread(PhantomReference pr, ReferenceQueue referenceQueue, String name) {
this.referenceQueue = referenceQueue;
this.name = name;
this.pr = pr;
}
@Override
public void run() {
try {
while (referenceQueue.remove(5000) == null) {
System.gc();
}
System.out.println(name + " found at " + System.nanoTime());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Found an excellent article on Finalizer by Jack Shirazi. The question answers itself once we go through the article.
http://www.fasterj.com/articles/finalizer1.shtml
To explain it in a nutshell: in the case of a non trivial finalize() method, even though the object is 'collected' in the first GC run, it is not physically deleted at that time. That happens on the next GC run. That's why the PR object will appear on the queue during second GC.
来源:https://stackoverflow.com/questions/12933134/java-phantomreference-referencequeue-and-finalize