如何使用PhantomReference作为finalize()替换

时间:2017-04-09 20:54:35

标签: java garbage-collection finalizer finalize phantom-reference

PhantomReference的Javadoc 8  规定:

  

幻像引用最常用于以比Java终结机制更灵活的方式安排 pre-mortem 清理操作。

所以我尝试创建一个调用符合垃圾收集条件的测试对象的 df = df.loc[:, df.isnull().sum() < 0.8*df.shape[0]] 方法的线程。 close()尝试让所有测试对象预先验证

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

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

我做错了什么?这是一个Java Bug吗?

可运行的测试代码:

我尝试创建最小,完整且可验证的示例,但它仍然很长。我在Windows 7 64位上使用close 32位。

java version "1.8.0_121"

预期产出:

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

2 个答案:

答案 0 :(得分:3)

幻像引用上的

get()方法总是返回null。

目前,幻像引用是入队对象,它已经被GC收集了引用。您需要在单独的对象中存储清理所需的数据(例如,您可以继承PhantomReference)。

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

与终结器不同,幻像引用无法恢复无法到达的对象。这是它的主要优势,虽然成本更复杂,支持代码。

答案 1 :(得分:2)

这是设计的。与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()

显示如何使用正确的习惯用法确保及时关闭资源,与public class DisplayActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_display); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); //Display list of hospitals String[] doctors = getResources().getStringArray(R.array.hospitalList); // Get array List ID ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, doctors); ListView lv = (ListView) findViewById(R.id.hospitalListView); lv.setAdapter(adapter); } } 不同,对象可以选择退出事后清理,这样可以更有效地使用正确的习惯用法,因为在这种情况下,没有最终确定后需要额外的GC循环来回收对象。