Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use PhantomReference as finalize() Replacement

Javadoc 8 for PhantomReference states:

Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

So I tried creating a thread that is calling the close() method of a Test Object that is eligible for garbage collection. The run() tries to get all Test Objects pre-mortem.

Actually the retrieved Test Objects are all null. The expected behavior is, that the Test Objects are retrieved and the closemethod is called.

No matter how many Test Objects you create there is not a single Test Object that could be caught pre-mortem (You have to increase the timeouts and call GC multiple times).

What am I doing wrong? Is this a Java Bug?

Runnable Test Code:

I tried to create a Minimal, Complete, and Verifiable example, but it's still quite long. I use java version "1.8.0_121" 32-bit on Windows 7 64-bit.

public class TestPhantomReference {

    public static void main(String[] args) throws InterruptedException {
        // Create AutoClose Thread and start it
        AutoCloseThread thread = new AutoCloseThread();
        thread.start();

        // Add 10 Test Objects to the AutoClose Thread
        // Test Objects are directly eligible for GC
        for (int i = 0; i < 2; i++) {
            thread.addObject(new Test());
        }

        // Sleep 1 Second, run GC, sleep 1 Second, interrupt AutoCLose Thread
        Thread.sleep(1000);
        System.out.println("System.gc()");
        System.gc();
        Thread.sleep(1000);
        thread.interrupt();
    }

    public static class Test {
        public void close() {
            System.out.println("close()");
        }
    }

    public static class AutoCloseThread extends Thread {
        private ReferenceQueue<Test> mReferenceQueue = new ReferenceQueue<>();
        private Stack<PhantomReference<Test>> mPhantomStack = new Stack<>();

        public void addObject(Test pTest) {
            // Create PhantomReference for Test Object with Reference Queue, add Reference to Stack
            mPhantomStack.push(new PhantomReference<Test>(pTest, mReferenceQueue));
        }

        @Override
        public void run() {
            try {
                while (true) {
                    // Get PhantomReference from ReferenceQueue and get the Test Object inside
                    Test testObj = mReferenceQueue.remove().get();
                    if (null != testObj) {
                        System.out.println("Test Obj call close()");
                        testObj.close();
                    } else {
                        System.out.println("Test Obj is null");
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("Thread Interrupted");
            }
        }
    }
}

Expected Output:

System.gc()
Test Obj call close()
close()
Test Obj call close()
close()
Thread Interrupted

Actual Output:

System.gc()
Test Obj is null
Test Obj is null
Thread Interrupted
like image 480
notes-jj Avatar asked Apr 09 '17 20:04

notes-jj


People also ask

What is an alternative for Finalize method in Java?

Phantom references are general replacement for finalize() . Many classes from Java runtime are already using them. Using Phantom references is a bit laborious, you have to maintain own reference list and postmortem processing thread.

What is the use of phantom reference in Java?

Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed. Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

What is finalize in Java?

Finalization is a feature of the Java programming language that allows you to perform postmortem cleanup on objects that the garbage collector has found to be unreachable. It is typically used to reclaim native resources associated with an object. Here's a simple finalization example: Copy. Copied to Clipboard.


2 Answers

This is by design. Unlike finalize(), which makes an object reachable again, objects referable by a Reference object only can not be made reachable again. So when you are going to manage a resource through it, you have to store the necessary information into another object. It’s not unusual, to use the Reference object itself for it.

Consider the following modifications to your test program:

public class TestPhantomReference {

    public static void main(String[] args) throws InterruptedException {
        // create two Test Objects without closing them
        for (int i = 0; i < 2; i++) {
            new Test(i);
        }
        // create two Test Objects with proper resource management
        try(Test t2=new Test(2); Test t3=new Test(3)) {
            System.out.println("using Test 2 and 3");
        }

        // Sleep 1 Second, run GC, sleep 1 Second
        Thread.sleep(1000);
        System.out.println("System.gc()");
        System.gc();
        Thread.sleep(1000);
    }

    static class TestResource extends PhantomReference<Test> {
        private int id;
        private TestResource(int id, Test referent, ReferenceQueue<Test> queue) {
            super(referent, queue);
            this.id = id;
        }
        private void close() {
            System.out.println("closed "+id);
        }
    }    
    public static class Test implements AutoCloseable {
        static AutoCloseThread thread = new AutoCloseThread();
        static { thread.start(); }
        private final TestResource resource;
        Test(int id) {
            resource = thread.addObject(this, id);
        }
        public void close() {
            resource.close();
            thread.remove(resource);
        }
    }

    public static class AutoCloseThread extends Thread {
        private ReferenceQueue<Test> mReferenceQueue = new ReferenceQueue<>();
        private Set<TestResource> mPhantomStack = new HashSet<>();

        public AutoCloseThread() {
            setDaemon(true);
        }
        TestResource addObject(Test pTest, int id) {
            final TestResource rs = new TestResource(id, pTest, mReferenceQueue);
            mPhantomStack.add(rs);
            return rs;
        }
        void remove(TestResource rs) {
            mPhantomStack.remove(rs);
        }

        @Override
        public void run() {
            try {
                while (true) {
                    TestResource rs = (TestResource)mReferenceQueue.remove();
                    System.out.println(rs.id+" not properly closed, doing it now");
                    mPhantomStack.remove(rs);
                    rs.close();
                }
            } catch (InterruptedException e) {
                System.out.println("Thread Interrupted");
            }
        }
    }
}

which will print:

using Test 2 and 3
closed 3
closed 2
System.gc()
0 not properly closed, doing it now
closed 0
1 not properly closed, doing it now
closed 1

showing how using the correct idiom ensures that resources are closed timely and, unlike finalize(), the object can opt out the post-mortem cleanup which makes using the correct idiom even more efficient, as in that case, no additional GC cycle is needed to reclaim the object after finalization.

like image 93
Holger Avatar answered Nov 05 '22 12:11

Holger


get() method on phantom references always return null.

At the moment phantom reference is enqueued object it was referencing is already collected by GC. You need to store data required to clean up in separate object (e.g. you can subclass PhantomReference).

Here you can find example code and more elaborate description about using PhantomReferences.

Unlike finalizer, phantom reference cannot resurrect unreachable object. This is its main advantage, though cost is more complicated supporting code.

like image 45
Alexey Ragozin Avatar answered Nov 05 '22 13:11

Alexey Ragozin