Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: PhantomReference, ReferenceQueue and finalize

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.

like image 605
shrini1000 Avatar asked Oct 17 '12 11:10

shrini1000


3 Answers

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.

like image 107
Marko Topolnik Avatar answered Sep 20 '22 03:09

Marko Topolnik


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.

like image 38
Shimi Bandiel Avatar answered Sep 17 '22 03:09

Shimi Bandiel


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();
            }
        }
    }

like image 21
abhinav Avatar answered Sep 17 '22 03:09

abhinav