Java 中的强引用、软引用、弱引用、虚引用

张贤 2020年03月10日 132次浏览
  • 强引用(Strong Reference)

    • 最普遍的引用:Object obj = new Object();
    • 内存不够时,即使抛出 OOM 也不会回收具有强引用的对象
    • 通过将对象设置为 null,使其被回收
  • 软引用(Soft Reference)

    • 对象处理有用但非必须的状态
    • 只有当内存空间不足时,GC 会回收该引用的对象的内存
    • 可以用来实现高速缓存
    • 可以配合引用队列来使用
        String str=new String("abc");
        SoftReference<String> softReference = new SoftReference<String>(str);
    
  • 弱引用(Weak Reference)

    • 非必须对象,比软引用更弱一些,生命周期更短
    • 无论内存是否充足,GC 扫描一旦发现弱引用,便会回收
    • 由于 GC 线程优先级很低,因此不一定会很快发现弱引用
    • 适用于引用偶尔被使用,且不会被垃圾收集影响的对象
    • 可以配合引用队列来使用
        String str=new String("abc");
        WeakReference<String> weakReference = new WeakReference<String>(str);
    
  • 虚引用(Phantom Reference)

    • 不会决定对象的生命周期
    • 虚引用就和没有任何引用一样,任何时候都有可能被垃圾回收器回收
    • 主要作用是跟踪对象被垃圾回收器的活动,起到哨兵的作用
    • 必须配合引用队列 ReferenceQueue 来使用。GC 在回收一个对象时,如果发现该对象具有虚引用,在回收之前会将该虚引用加入到与之关联的引用队列中,程序可以通过判断引用队列中是否已经加入虚引用来判断被引用的对象是否已经被 GC 回收
        String str=new String("abc");
        ReferenceQueue queue=new ReferenceQueue();
        PhantomReference reference = new PhantomReference(str,queue);
    
  • 引用队列(ReferenceQueue)

    • 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
    • 存储关联且被 GC 的软引用,弱引用以及虚引用
    • 下面是一个例子:

定义一个一个普通类

//创建一个普通对象的类
public class NormalObject {
    public String name;

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

    //重写 finalize 方法
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizing obj " + name);
    }
}

定义一个弱引用的类

public class NormalObjectWeakReference extends WeakReference<NormalObject> {
    public String name;

    //在弱引用里面引用 NormalObject 对象
    public NormalObjectWeakReference(NormalObject referent, ReferenceQueue<NormalObject> rq) {
        super(referent, rq);
        this.name = referent.name;
    }

    //重写 finalize 方法
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizing NormalObjectWeakReference " + name);
    }
}

最后定义一个测试类

public class ReferenceQueueTest {
    private static ReferenceQueue<NormalObject> rq=new ReferenceQueue<>();

    //遍历引用队列,并输出每一个弱引用的名字和实际引用的对象
    private static void checkQueue(){
        Reference<NormalObject> ref=null;
        while ((ref= (Reference<NormalObject>) rq.poll())!=null){
            if ((ref!=null)){
                System.out.println("In queue: "+((NormalObjectWeakReference)ref).name);
                System.out.println("reference object: "+ref.get());
            }
        }
    }

    public static void main(String[] args) {
        ArrayList<WeakReference<NormalObject>> weakLists=new ArrayList<>();
        //向队列里添加弱引用
        for (int i = 0; i < 3; i++) {
            weakLists.add(new NormalObjectWeakReference(new NormalObject("Weak "+i),rq));
            System.out.println("Created weak: "+weakLists.get(i));
        }

        System.out.println("first time");
        checkQueue();//第一次由于还没有进行 GC,所以引用队列为空,checkQueue 方法无输出
        System.gc();//这里触发对象的 finalize 方法,并把弱引用添加到引用队列
        try {
            //这里睡眠 1 秒,让 GC 有足够时间运行完成
            Thread.currentThread().sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("second time");//第二次由于经过了 GC,所以引用队列不为空,但是弱引用所指向的对象为空
        checkQueue();
    }
}

输出如下:

Created weak: gc.NormalObjectWeakReference@60e53b93
Created weak: gc.NormalObjectWeakReference@5e2de80c
Created weak: gc.NormalObjectWeakReference@1d44bcfa
first time
Finalizing obj Weak 2
Finalizing obj Weak 1
Finalizing obj Weak 0
second time
In queue: Weak 2
reference object: null
In queue: Weak 1
reference object: null
In queue: Weak 0
reference object: null

具体原因在代码注释中已经写明,这里就不展开了。
ReferenceQueue 的意义在于可以在外部对 ReferenceQueue 进行监控,如果有对象即将被回收,那么相应的 reference 对象(软引用,弱引用)就会被添加到 ReferenceQueue 中,我们通过不断轮询 ReferenceQueue 的元素,判断元素里的 get() 方法是否为 null,来判断对象是否被回收;如果不为 null ,则对象已经被回收,可以拿到拿到 reference 对象再做一些处理(这一点不适用于 Phantom Reference,因为 Phantom Reference 的 get() 方法用云返回 null,因此 Phantom Reference 只能通过 Queue 来判断,这也是为什么 Phantom Reference 只带有 Queue 的构造函数的原因)。