Java/Android中有四种引用类型,分别是:
Strong reference - 强引用
Soft Reference - 软引用
Weak Reference - 弱引用
Phantom Reference - 虚引用

不同的引用类型有着不同的特性,同时也对应着不同的使用场景。

Strong reference - 强引用

实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。一般情况下,当对内存中的对象不再有任何强引用指向它时,垃圾回收机器开始考虑可能要对此内存进行的垃圾回收。如当进行编码:a = null,此时,刚刚在堆中分配地址并新建的a对象没有其他的任何引用,当系统进行垃圾回收时,堆内存将被垃圾回收。

SoftReference、WeakReference、PhantomReference都是类java.lang.ref.Reference的子类。Reference作为抽象基类,定义了其子类对象的基本操作。Reference子类都具有如下特点:

  • Reference子类不能无参化直接创建,必须至少以强引用对象为构造参数,创建各自的子类对象;
  • 因为1中以强引用对象为构造参数创建对象,因此,使得原本强引用所指向的堆内存中的对象将不再只与强引用本身直接关联,与Reference的子类对象的引用也有一定联系。且此种联系将可能影响到对象的垃圾回收。

根据不同的子类对象对其指示对象(强引用所指向的堆内存中的对象)的垃圾回收不同的影响特点,分别形成了三个子类,即SoftReference、WeakReference和PhantomReference

Soft Reference - 软引用

软引用的一般使用形式如下:
A a = new A();
SoftReference srA = new SoftReference(a);
通过对象的强引用为参数,创建了一个SoftReference对象,并使栈内存中的wrA指向此对象。
此时,进行如下编码:a = null,对于原本a所指向的A对象的垃圾回收有什么影响呢?
先直接看一下下面一段程序的输出结果:

public class ReferenceTest {
    public static void main(String[] args) {
        A a = new A();
        SoftReference<A> srA = new SoftReference<A>(a);
        a = null;
        if (srA.get() == null) {
            System.out.println("a对象进入垃圾回收流程");
        } else {
            System.out.println("a对象尚未被回收" + srA.get());
        }
        // 垃圾回收
        System.gc();

        if (srA.get() == null) {
            System.out.println("a对象进入垃圾回收流程");
        } else {
            System.out.println("a对象尚未被回收" + srA.get());
        }
    }
}

class A {

}
复制代码
输出结果为:
a对象尚未被回收A@4807ccf6
a对象尚未被回收A@4807ccf6
复制代码

当 a = null后,堆内存中的A对象将不再有任何的强引用指向它,但此时尚存在srA引用的对象指向A对象。当第一次调用srA.get()方法返回此指示对象时,由于垃圾回收器很有可能尚未进行垃圾回收,此时get()是有结果的,这个很好理解。当程序执行System.gc();强制垃圾回收后,通过srA.get(),发现依然可以得到所指示的A对象,说明A对象并未被垃圾回收。那么,软引用所指示的对象什么时候才开始被垃圾回收呢?需要满足如下两个条件:
1.当其指示的对象没有任何强引用对象指向它;
2.当虚拟机内存不足时。
因此,SoftReference变相的延长了其指示对象占据堆内存的时间,直到虚拟机内存不足时垃圾回收器才回收此堆内存空间。

Weak Reference - 弱引用

同样的,软引用的一般使用形式如下:
A a = new A();
WeakReference wrA = new WeakReference(a);
当没有任何强引用指向此对象时, 其垃圾回收又具有什么特性呢?

public class ReferenceTest {
    public static void main(String[] args) {
        A a = new A();
        WeakReference<A> wrA = new WeakReference<A>(a);
        a = null;
        if (wrA.get() == null) {
            System.out.println("a对象进入垃圾回收流程");
        } else {
            System.out.println("a对象尚未被回收" + wrA.get());
        }
        // 垃圾回收
        System.gc();

        if (wrA.get() == null) {
            System.out.println("a对象进入垃圾回收流程");
        } else {
            System.out.println("a对象尚未被回收" + wrA.get());
        }
    }
}

class A {

}
复制代码
输出结果为:
a对象尚未被回收A@52e5376a
a对象进入垃圾回收流程
复制代码

输出的第一条结果解释同上。当进行垃圾回收后,wrA.get()将返回null,表明其指示对象进入到了垃圾回收过程中。因此,对弱引用特点总结为:
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。
那么,依据此特点,很可能有疑问:WeakReference存在又有什么意义呢? 其主要使用场景见于:当前已有强引用指向强引用对象,此时由于业务需要,需要增加对此对象的引用,同时又不希望改变此引用的垃圾回收时机,此时WeakReference正好符合需求,常见于一些与生命周期的场景中。

ReferenceQueue

对于SoftReference和WeakReference,还有一个构造器参数为ReferenceQueue,当SoftReference或WeakReference所指示的对象确实被垃圾回收后,其引用将被放置于ReferenceQueue中。注意上文中,当SoftReference或WeakReference的get()方法返回null时,仅是表明其指示的对象已经进入垃圾回收流程,此时对象不一定已经被垃圾回收。而只有确认被垃圾回收后,其引用才会被放置于ReferenceQueue中。

public static void main(String[] args) {
        A a = new A();
        ReferenceQueue referenceQueue = new ReferenceQueue();
        WeakReference<Object> weakReference = new WeakReference<Object>(a, referenceQueue);

        a = null;
        if(weakReference.get() == null){
            System.out.println("a对象进入垃圾回收流程");
        }else {
            System.out.println("a对象尚未被回收" + weakReference.get());
        }

        System.out.println("rq item:" + referenceQueue.poll());

        System.gc();

        if(weakReference.get() == null){
            System.out.println("a对象进入垃圾回收流程");
        }else {
            System.out.println("a对象尚未被回收" + weakReference.get());
        }

        System.out.println("rq item:" + referenceQueue.poll());
    }

    public static class A{
        @Override
        protected void finalize() throws Throwable {
            System.out.println("A finalize");
            super.finalize();
        }
    }
复制代码
输出结果为:
a对象尚未被回收@4554617c
rq item:null
a对象进入垃圾回收流程
rq item:null
A finalize
复制代码

当调用System.gc()后,输出‘a对象进入垃圾回收流程’说明a已经开始进入了垃圾回收流程,但是还未被真正回收,因为Java对象被回收时会调用期finalize方法;此时我们立刻调用referenceQueue.poll()得到的是空,这样验证了对象只有gc已回收后WeakReference或SoftReference才会被加入ReferenceQueue。

现在我们再gc后,进行一段时间的延时后再调用referenceQueue.poll():
public static void main(String[] args) {
        A a = new A();
        ReferenceQueue referenceQueue = new ReferenceQueue();
        WeakReference<Object> weakReference = new WeakReference<Object>(a, referenceQueue);

        a = null;
        if(weakReference.get() == null){
            System.out.println("a对象进入垃圾回收流程");
        }else {
            System.out.println("a对象尚未被回收" + weakReference.get());
        }

        System.out.println("rq item:" + referenceQueue.poll());

        System.gc();

        if(weakReference.get() == null){
            System.out.println("a对象进入垃圾回收流程");
        }else {
            System.out.println("a对象尚未被回收" + weakReference.get());
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("rq item:" + referenceQueue.poll());
    }

    public static class A{
        @Override
        protected void finalize() throws Throwable {
            System.out.println("A finalize");
            super.finalize();
        }
    }
复制代码
输出结果
a对象尚未被回收@4554617c
rq item:null
a对象进入垃圾回收流程
A finalize
rq item:@74a14482
复制代码

此时referenceQueue中存在了WeakReference,实验的结果也就不言而喻了。

PhantomReference虚引用

与SoftReference或WeakReference相比,PhantomReference主要差别体现在如下几点:


PhantomReference只有一个构造函数PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必须结合ReferenceQueue; 不管有无强引用指向PhantomReference的指示对象,PhantomReference的get()方法返回结果都是null。

public class ReferenceTest {
    public static void main(String[] args) {
        A a = new A();
        ReferenceQueue<A> rq = new ReferenceQueue<A>();
        PhantomReference<A> prA = new PhantomReference<A>(a, rq);
        System.out.println("prA.get():" + prA.get());
    
        a = null;
        
        System.gc();
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("rq item:" + rq.poll());

    }
}

class A {

}
复制代码
输出结果为:
prA.get():null
rq item:java.lang.ref.PhantomReference@1da12fc0
复制代码

代码中的Thread.sleep(1);作用与上例中相同,都是确保垃圾回收线程能够执行。否则,进进入垃圾回收流程而没有真正被垃圾回收的指示对象的虚引用是不会被加入到PhantomReference中的。

与WeakReference相同,PhantomReference并不会改变其指示对象的垃圾回收时机。ReferenceQueue的作用主要是用于监听Java对象是否已经被垃圾回收。