【Java基础】四种引用(强引用、软引用、弱引用、虚引用)

java中的引用类似C/C++中的指针,都是指向内存中的某个对象,通过该引用可以直接操作对应的对象。引用和GC有着直接关系,根据可达性分析算法(从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用),一个对象没有引用关联,则GC可以将其回收。

1、强引用

引用的默认方式,即是强引用,创建一个对象并把这个对象赋给一个引用变量,如Object obj = new Object();

强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象.

如果分配了一个大对象,如String[] strings = new String[1024 * 1024 * 600](-Xms512M -Xmx512M);jvm会抛OOM。

如果想中断强引用和某个对象之间的关联,需要显式地将引用赋值为null,如HashMap的clear操作

public void clear() {
    Node<K,V>[] tab;
    modCount++;
    if ((tab = table) != null && size > 0) {
        size = 0;
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;//此处显式地将引用赋值为null,断开了强引用关系,便于GC
    }
}

2、软引用(SoftReference)

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用常被用作缓存,如图片、网页缓存等。

用法为:

byte[] bytes = new byte[1024 * 1024 * 100];

SoftReference<byte[]> softReference = new SoftReference<>(bytes);

此时对象有两个引用,第1行的强引用,第3行的软引用。由于有强引用存在,对象此时是不会被回收的。

此时将bytes = null;对象只有软引用,当内存不足时,是可以回收该对象的。

byte[] bytes = new byte[1024 * 1024 * 100];

SoftReference<byte[]> softReference = new SoftReference<>(bytes);
System.out.println(softReference.get());//[B@d716361

bytes = null;

System.gc();

System.out.println(softReference.get());//[B@d716361

byte[] bytes2 = new byte[1024 * 1024 * 300];

System.out.println(softReference.get());//null

可以看到,第10行get时还是可以取到值的,因为此时内存充裕。当第12行需要分配一个大对象时,那对不起,要把你这个软引用对象回收了,因为此时内存快不够了。垃圾收集线程会在虚拟机抛出OOM之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用的软引用对象,对那些刚刚构建的或刚刚使用过的“新”软引用对象会被虚拟机尽可能保留。

软引用使用的一个例子可以参见Android的图片缓存

3、弱引用(WeakReference)

弱引用比软引用优先级更低,gc时,不管内存还充不充裕,都会回收弱引用对象。

byte[] bytes = new byte[1024 * 1024 * 100];

WeakReference<byte[]> weakReference = new WeakReference<>(bytes);
bytes = null;
System.out.println(weakReference.get());//[B@d716361

System.gc();

System.out.println(weakReference.get());//null

4、虚引用(PhantomReference)

Phantom有幻觉、幽灵之意。听名字就知道是这几种引用中最弱的,跟不存在似的。如果对象仅仅存在虚引用,那么GC可以随时回收它,明白为什么叫Phantom了吧。

看下PhantomReference的代码:

public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

PhantomReference就一个方法get(直接返回null,所以调用了它也没啥意义),一个构造函数(入参有个引用队列,说明了PhantomReference必须和引用队列一起使用)

5、引用队列(ReferenceQueue)

查看软引用、弱引用、虚引用的代码可以看到,软引用、弱引用可以和引用队列一起使用,也可以不一起,而虚引用必须和引用队列一起使用。那么引用队列是什么?

引用队列,顾名思义,引用组成的队列,引用指的是Reference,SoftReference、WeakReference、PhantomReference均继承此类,该类中有几个属性:

private volatile Reference<? extends T> head = null;
private T referent;         /* Treated specially by GC */

volatile ReferenceQueue<? super T> queue;

/* When active:   NULL
 *     pending:   this
 *    Enqueued:   next reference in queue (or this if last)
 *    Inactive:   this
 */
@SuppressWarnings("rawtypes")
volatile Reference next;

head是队列维持的头指针,referent即各自引用的对象,queue是创建引用时传入的引用队列,next指向下一个引用。

byte[] bytes = new byte[1024 * 1024 * 100];
ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
bytes = null;

在上面的代码中,weakReference所引用的对象会在下次GC的时候被回收掉,自己也会被加入rq的队列(每个引用被放进来的时候,会被加到前一个引用的前面,即Reference的next指向前一个加入进来的Reference,说是加入进队列,其实是将队列的head和各个Reference中的next串联起来形成链表)。为什么要这样做呢,weakReference引用的对象虽然在下次GC的时候被清除掉了,它自身也没有存在的价值了,但是weakReference本身也是强引用类型啊,自身也是占用内存的。所以weakReference被放到引用队列的时候,我们就可以把它取出来清掉了。

ReferenceQueue提供了三种取出Reference的方法:

public Reference<? extends T> poll();//非阻塞,取出head锁指向的Reference
public Reference<? extends T> remove();//阻塞,直到head不为null,即队列中有Reference
public Reference<? extends T> remove(long timeout);//阻塞,和上一个一样,不过加了个超时时间

具体使用可以参见WeakHashMap分析

6、总结

  • 四种引用的可及性优先级:强引用>软引用>弱引用>虚引用。当一个对象同时有多种引用类型时,可及性按高优先级处理。如
Object object = new Object();
WeakReference<Object> weakReference = new WeakReference<>(object);

同时有强引用和弱引用存在,那么这个对象就是强可及的。GC的时候不会回收。

  • 软引用和弱引用最好和引用队列一起使用,这样你能知道引用对象何时被回收及做出一些相应处理。虚引用必须和引用队列一起使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章