8

PhantomReference的 Javadoc 8 状态:

幻影引用最常用于以比 Java 终结机制更灵活的方式调度事前清理操作。

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

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

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

我究竟做错了什么?这是一个Java错误吗?

可运行的测试代码:

我试图创建一个Minimal, Complete, and Verifiable example,但它仍然很长。我java version "1.8.0_121"在 Windows 7 64 位上使用 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
4

2 回答 2

7

这是设计使然。与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 循环来回收最终确定后的对象。

于 2017-04-27T13:55:42.883 回答
4

get()幻像引用上的方法总是返回 null。

目前幻影引用已入队,它所引用的对象已被 GC 收集。您需要将清理所需的数据存储在单独的对象中(例如,您可以子类化PhantomReference)。

在这里PhantomReference您可以找到示例代码和有关使用s 的更详细说明。

与终结器不同,幻影引用不能复活无法访问的对象。这是它的主要优势,尽管成本是更复杂的支持代码。

于 2017-04-09T22:24:57.727 回答