Understanding java references


Java中的4种引用类型,中文名为强引用,软引用,弱引用以及虚引用,虚引用也就是传说中的幽灵引用。 示例

Java的4种引用类型与GC有着密切的关系,接下来逐一来看看其定义和使用场景。

StrongReference是Java的默认引用,它尽可能长时间的存活于JVM堆中,当没有任何对象指向它时,GC后会被回收。JVM采用Finalizer来管理每个强引用对象,将其被标记要清理时加入ReferenceQueue后逐一调用该对象的finalize()方法。

SoftReference, WeakReference和PhantomReference继承自Reference类。当垃圾回收器正在考察的对象只能通过某个Reference才可获得时,它们为垃圾回收器提供了不同级别的间接指示。

对象是可获得的(reachable)指该对象在程序中某处被直接或间接的引用了。如果一个对象是可获得的,垃圾回收器就不能释放它,因为它仍在程序中使用。如果一个对象不是可获得的,表示程序中已经不存在对该对象的引用,这时将其回收是安全的。

SoftReference, WeakReference和PhantomReference由强到弱对应不同级别的可获得性:SoftReference用于实现内存敏感的高速缓存;而WeakReference用于实现规范映射而设计,规范映射中对象的实例可以在程序的多处使用以节省空间。

SoftReference是只在内存紧张时才收集软可及对象。所以SoftReference比WeakHashMap采用的弱引用策略更适合于实现缓存机制。WeakReference适用于占有大量内存,但通过垃圾回收功能回收以后很容易重新创建的场景。

我们说,HashMap采用的是类似强引用的强键来管理,也就是说即使作为key的对象已经没有被任何对象引用了,它仍然会保存在 HashMap中,在某些情况(如内存缓存)下,这些过期的对象会造成内存泄露。WeakHashMap采取的策略是,只要作为key的对象已经超出了生命周期,就不会阻止垃圾回收器对该对象的处理,即使在内存并不紧张的情况下。不过因为GC是一个优先级很低的线程,所以不能保证会立刻被清除。

测试以下代码试试看:

package com.dunkcoder.test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;

import org.junit.Test;

public class ReferenceTest {

    class BigObject {

        private static final int SIZE = 100000;
        @SuppressWarnings("unused")
        private double[] dblArr = new double[SIZE];
        private String name;

        public BigObject(String name) {
            this.name = name;
        }

        public BigObject(String name, int size) {
            this.name = name;
            dblArr = new double[size];
        }

        public String toString() {
            return this.name;
        }

        // 覆盖finalize()函数,在finalize()中输出打印信息,方便追踪
        protected void finalize() {
            System.out.println("Finalizing " + this.name);
        }
    }

    @Test
    public void strongReferenceNotGc() {

        Object referent = new BigObject("strongReferenceNotGC");
        // StrongReference
        Object strongReference = referent;
        assertSame(referent, strongReference);

        referent = null;
        System.gc();

        // GC后不会被回收
        assertNotNull(strongReference);
    }

    @Test
    public void strongReferenceGc() {

        Object referent = new BigObject("strongReferenceGC");
        referent = null;
        System.gc();

        // GC后被回收
        assertNull(referent);
    }

    @Test
    public void weakReference() {
        Object referent = new BigObject("weakReference");
        WeakReference<Object> weakReference = new WeakReference<Object>(referent);

        assertSame(referent, weakReference.get());

        referent = null;
        System.gc();

        // 被包装成WeakReference的对象,在没有强引用的时候,GC会将它们标记为可以被回收,在被回收之前会执行finalize()方法  
        // 应用场景:缓存,key一旦失去了其他的引用,缓存里的东西就可以随时消失
        assertNull(weakReference.get());
    }

    @Test
    public void weakHashMap() throws InterruptedException {
        Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>();
        Object key = new BigObject("weakHashMap key");
        Object value = new Object();
        weakHashMap.put(key, value);

        assertTrue(weakHashMap.containsValue(value));

        key = null;
        System.gc();

        // 等待无效entries进入ReferenceQueue,以便WeakHashMap.getTable()时被清理
        Thread.sleep(1000);

        // key设置为null后,相关的entries在GC后被回收
        assertFalse(weakHashMap.containsValue(value));
    }

    @Test
    public void softReference() {

        SoftReference<Object> softRerference = new SoftReference<Object>(new BigObject("softReference"));
        assertNotNull(softRerference.get());

        System.gc();
        assertNotNull(softRerference.get());

        // OutOfMemory ...
        int i = 0;
        while (true) {
            try {
                ++i;
                new BigObject("OutOfMemory", 100000000);
            } catch (Throwable e) {
                System.out.println("OutOfMemory after " + i + " times");
                e.printStackTrace();
                break;
            }
        }

        // SoftReference只有在OutOfMemory之前才会被回收,即在OOM前也就不会执行finalize()方法
        assertNull(softRerference.get());
    }

    @Test
    public void phantomReferenceAlwaysNull() {
        // PhantomReference唯一的用处就是跟踪referent何时被入队(enqueue)到ReferenceQueue中
        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(new BigObject("phantomReferenceAlwaysNull"), queue);

        assertNull(phantomReference.get());

        assertNull(queue.poll());
        System.gc();
        assertNull(queue.poll());
    }

    @Test
    public void referenceQueueWithWeakReference() throws InterruptedException {
        Object referent = new BigObject("referenceQueueWithWeakReference");
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        Reference<Object> ref = new WeakReference<Object>(referent, referenceQueue);

        assertFalse(ref.isEnqueued());
        Reference<? extends Object> polled = referenceQueue.poll();
        assertNull(polled);

        referent = null;
        System.gc();

        assertTrue(ref.isEnqueued());
        Reference<? extends Object> removed = referenceQueue.remove();
        assertNotNull(removed);
        assertSame(ref, removed);
        assertNull(removed.get());
    }

    @Test
    public void referenceQueueWithSoftReference() throws InterruptedException {
        Object referent = new BigObject("referenceQueueWithWeakReference");
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        Reference<Object> ref = new SoftReference<Object>(referent, referenceQueue);

        assertFalse(ref.isEnqueued());
        Reference<? extends Object> polled = referenceQueue.poll();
        assertNull(polled);

        referent = null;
        // OutOfMemory ...
        int i = 0;
        while (true) {
            try {
                ++i;
                new BigObject("OutOfMemory", 100000000);
            } catch (Throwable e) {
                System.out.println("OutOfMemory after " + i + " times");
                e.printStackTrace();
                break;
            }
        }
        assertTrue(ref.isEnqueued());
        Reference<? extends Object> removed = referenceQueue.remove();
        assertNotNull(removed);
        assertSame(ref, removed);
        assertNull(removed.get());
    }

}

以上,您有任何问题或建议,请给我写邮件


Yinwer /
Published under (CC) BY-NC-SA