Java: PhantomReference, ReferenceQueue and finalize

南笙酒味 提交于 2019-12-01 05:21:16

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!