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

在Java层面,一共有四种引用:强引用、软引用、弱引用、虚引用,这几种引用的生命周期由强到弱。转换关系大致如下图所示:

强引用(Strong Reference)

  就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了。

软引用(Soft Reference)

   实现类为:SoftReference。只有当JVM认为内存不足时,才会试图回收软引用指向的对象,JVM会确保在抛出OutOfMemoryError之前,清理软引用指向的对象。(适合做缓存)通过下面的代码可以验证:

import java.lang.ref.SoftReference;
public class SoftReferenceTest {
    //-Xms25m -Xmx25m -Xmn20m -XX:+PrintGCDetails 
    public static void main(String[] args) {
        softReference();
    }
    

    public static void softReference() {
        //申请10M的数据
        byte[] referent = new byte[1024*1024*10];
        SoftReference<Object> softRerference = new SoftReference<Object>(referent);
        referent = null;
        //不会回收软引用的数据,
        System.gc();
        //软引用的对象在内存充足的情况下不会回收
        if(softRerference.get() != null){
            System.out.println("true");
        }else{
            System.out.println("false");
        }
        //因为空间不足,会回收软引用的数据
        byte[] another = new byte[1024*1024*10];
        if(softRerference.get() != null){
            System.out.println("true");
        }else{
            System.out.println("false");
        }
        System.out.println("end");
    }
}

弱引用(Weak Reference):

   实现类为:WeakReference。可以用来构建一种没有特定约束的关系,同样是缓存实现的选择(WeekHashMap就是采用弱引用的方式实现的)。JVM一旦发现了某个对象只有弱引用与之关联,不管当前内存空间足够与否,都会回收它的内存。下面代码可以验证:

import java.lang.ref.WeakReference;

public class WeakReferenceTest {
    
    // -Xms25m -Xmx25m -Xmn20m -XX:+PrintGCDetails
    public static void main(String[] args) {
        weekReference();
    }

    public static void weekReference() {
        // 申请10M的数据
        byte[] referent = new byte[1024 * 1024 * 10];
        WeakReference<Object> softRerference = new WeakReference<Object>(referent);
        referent = null;
        //弱引用的数据,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
        System.gc();
        // 软引用的对象在内存充足的情况下不会回收
        if (softRerference.get() != null) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }
}

幻象引用(Phantom Reference)

  实现类为:PhantomReference。提供了一种确保对象被finalize以后,做某些事情的机制。(Java平台自身的Cleaner机制)如:申请堆外内存时,在JVM堆中会创建一个对应的Cleaner对象,这个Cleaner类继承了PhantomReference,当DirectByteBuffer对象被回收时,可以执行对应的Cleaner对象的clean方法,做一些后续工作,这里是释放之前申请的堆外内存。

引用何时被加到ReferenceQueue队列里

  在构造软引用,弱引用和幻象引用的时候,可以传入一个ReferenceQueue的对象,这个队列是用来做什么的呢?当软引用,弱引用和幻象引用所引用的对象被回收之后,对应的SoftReference,WeakReference,PhantomReference 对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量Reference对象带来的内存泄漏。而这个队列就是由JVM将引用对象加入到队列里,由JVM将Reference对象清理。加入队列是由ReferenceHandler这个线程来来做的,代码如下图所示:

  • tryHandlePending方法的代码如下:
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) { //pending由JVM进行赋值
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered; //将pending的值往下移
                    r.discovered = null;
                } else {
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }
//Cleaner 类型的直接掉用clean对象,不会加入到队列里了
        if (c != null) {
            c.clean();
            return true;
        }
//这里将Reference对象加入到队列里
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

Finalizer引用

  Finalizer继承Reference,Finalizer在我们的系统里无法被构造(类被定义成package final 类型),Finalizer的实例是一个双向链表的结构,内部有prev与next指针,提供了add与remove方法将对象增加到链表与从链表中删除对象。任何类只要实现了Object类里的finalize方法,JVM在初使化这个对象的时候(调用构造方法的时候),会构造一个Finalizer对象,通过调用Finalizer的register方法,代码如下:


在构造方法里,会调用add方法,将Finalizer对象加入到链表里,代码如下:
,我们分析dump内存的时候,经常能看到 java.lang.ref.Finalizer占用的内存大小远远排在前面,就是因为系统里构造了大量的实现了finalize方法的对象。

何时被加入到ReferenceQueue里

  当gc发生的时候,gc算法会判断对象是不是只被Finalizer类引用,如果这个类仅仅被Finalizer对象引用的时候,说明这个对象在不久的将来会被回收了现在可以执行它的finalize方法了,于是会将这个Finalizer对象放到Finalizer类的ReferenceQueue里,但是这个f类对象其实并没有被回收,因为Finalizer这个类还对他们持有引用,在gc完成之前,jvm会调用ReferenceQueue里的lock对象的notify方法(当ReferenceQueue为空的时候,FinalizerThread线程会调用ReferenceQueue的lock对象的wait方法直到被jvm唤醒)

何时调用finalize方法

  Finalizer类里定义了FinalizerThread,用于将ReferenceQueue里的对象取出并执行finalize方法。具体代码如下:


软引用的具体回收时机可以参考:https://www.jianshu.com/p/e46158238a77
参考文章:https://time.geekbang.org/column/article/6970
https://www.jianshu.com/p/e46158238a77
https://www.jianshu.com/p/7200da8b043f
https://mp.weixin.qq.com/s/fftHK8gZXHCXWpHxhPQpBg

推荐阅读更多精彩内容