java - 如何使用 PhantomReference 作为 finalize() 的替代

标签 java garbage-collection finalizer finalize phantom-reference

Javadoc 8 PhantomReference 状态:

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.

所以我尝试创建一个线程来调用符合垃圾回收条件的测试对象的 close() 方法。 run() 尝试获取所有测试对象pre-mortem

实际上检索到的测试对象都是null。预期的行为是检索测试对象并调用 close 方法。

无论您创建多少个测试对象,都没有一个测试对象可以被捕获预先分析(您必须增加超时并多次调用 GC)。

我做错了什么?这是 Java 错误吗?

可运行的测试代码:

我尝试创建一个最小、完整且可验证的示例,但它仍然很长。我在 Windows 7 64 位上使用 java 版本“1.8.0_121” 32 位。

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

预期输出:

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

实际输出:

System.gc()
Test Obj is null
Test Obj is null
Thread Interrupted

最佳答案

这是设计使然。与使对象再次可达的 finalize() 不同,只能由 Reference 对象引用的对象不能再次可达。所以当你要通过它管理资源时,你必须将必要的信息存储到另一个对象中。为它使用 Reference 对象本身并不罕见。

考虑对您的测试程序进行以下修改:

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

将打印:

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

显示如何使用正确的习惯用法确保资源及时关闭,并且与 finalize() 不同,对象可以选择退出事后清理,这使得使用正确的习惯用法更加高效,因为在这种情况下,不需要额外的 GC 周期来在完成后回收对象。

关于java - 如何使用 PhantomReference 作为 finalize() 的替代,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43311825/

相关文章:

java - 如何执行我的 java 桌面应用程序的单个实例?

c# - 如何确保 winform 被垃圾收集?

Java:新生代使用 100%,Eden 空间使用 100%,From 空间使用 100%

java - 我应该使用哪个 JVM 参数来指示 JVM 开始垃圾收集

c# - 拥有终结器的开销 - 在 Dispose 中有/没有 SuppressFinalize

c# - GC.SuppressFinalize 是否保持对象的根

java - 无法在 GridView 中显示包含 Pointer 字段的 Parse.com 对象的数据 - Android

java - JPA查询优化

java - 在 init 之外的 java 中创建对象

node.js - nodejs垃圾收集输出