Java中弱引用、軟引用、虛引用、強引用、 Finalizer引用

在Java層面,一共有四種引用:強引用、軟引用、弱引用、虛引用,這幾種引用的生命周期由強到弱。轉(zhuǎn)換關(guān)系大致如下圖所示:

強引用(Strong Reference)

??就是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還“活著”,垃圾收集器不會碰這種對象。對于一個普通的對象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為 null,就是可以被垃圾收集的了。

軟引用(Soft Reference)

?? 實現(xiàn)類為:SoftReference。只有當JVM認為內(nèi)存不足時,才會試圖回收軟引用指向的對象,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的數(shù)據(jù)
        byte[] referent = new byte[1024*1024*10];
        SoftReference<Object> softRerference = new SoftReference<Object>(referent);
        referent = null;
        //不會回收軟引用的數(shù)據(jù),
        System.gc();
        //軟引用的對象在內(nèi)存充足的情況下不會回收
        if(softRerference.get() != null){
            System.out.println("true");
        }else{
            System.out.println("false");
        }
        //因為空間不足,會回收軟引用的數(shù)據(jù)
        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):

?? 實現(xiàn)類為:WeakReference。可以用來構(gòu)建一種沒有特定約束的關(guān)系,同樣是緩存實現(xiàn)的選擇(WeekHashMap就是采用弱引用的方式實現(xiàn)的)。JVM一旦發(fā)現(xiàn)了某個對象只有弱引用與之關(guān)聯(lián),不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。下面代碼可以驗證:

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的數(shù)據(jù)
        byte[] referent = new byte[1024 * 1024 * 10];
        WeakReference<Object> softRerference = new WeakReference<Object>(referent);
        referent = null;
        //弱引用的數(shù)據(jù),在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存
        System.gc();
        // 軟引用的對象在內(nèi)存充足的情況下不會回收
        if (softRerference.get() != null) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }
}

幻象引用(Phantom Reference)

??實現(xiàn)類為:PhantomReference。提供了一種確保對象被finalize以后,做某些事情的機制。(Java平臺自身的Cleaner機制)如:申請堆外內(nèi)存時,在JVM堆中會創(chuàng)建一個對應的Cleaner對象,這個Cleaner類繼承了PhantomReference,當DirectByteBuffer對象被回收時,可以執(zhí)行對應的Cleaner對象的clean方法,做一些后續(xù)工作,這里是釋放之前申請的堆外內(nèi)存。

引用何時被加到ReferenceQueue隊列里

??在構(gòu)造軟引用,弱引用和幻象引用的時候,可以傳入一個ReferenceQueue的對象,這個隊列是用來做什么的呢?當軟引用,弱引用和幻象引用所引用的對象被回收之后,對應的SoftReference,WeakReference,PhantomReference 對象已經(jīng)不再具有存在的價值,需要一個適當?shù)那宄龣C制,避免大量Reference對象帶來的內(nèi)存泄漏。而這個隊列就是由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,F(xiàn)inalizer在我們的系統(tǒng)里無法被構(gòu)造(類被定義成package final 類型),F(xiàn)inalizer的實例是一個雙向鏈表的結(jié)構(gòu),內(nèi)部有prev與next指針,提供了add與remove方法將對象增加到鏈表與從鏈表中刪除對象。任何類只要實現(xiàn)了Object類里的finalize方法,JVM在初使化這個對象的時候(調(diào)用構(gòu)造方法的時候),會構(gòu)造一個Finalizer對象,通過調(diào)用Finalizer的register方法,代碼如下:


在構(gòu)造方法里,會調(diào)用add方法,將Finalizer對象加入到鏈表里,代碼如下:
,我們分析dump內(nèi)存的時候,經(jīng)常能看到 java.lang.ref.Finalizer占用的內(nèi)存大小遠遠排在前面,就是因為系統(tǒng)里構(gòu)造了大量的實現(xiàn)了finalize方法的對象。

何時被加入到ReferenceQueue里

??當gc發(fā)生的時候,gc算法會判斷對象是不是只被Finalizer類引用,如果這個類僅僅被Finalizer對象引用的時候,說明這個對象在不久的將來會被回收了現(xiàn)在可以執(zhí)行它的finalize方法了,于是會將這個Finalizer對象放到Finalizer類的ReferenceQueue里,但是這個f類對象其實并沒有被回收,因為Finalizer這個類還對他們持有引用,在gc完成之前,jvm會調(diào)用ReferenceQueue里的lock對象的notify方法(當ReferenceQueue為空的時候,F(xiàn)inalizerThread線程會調(diào)用ReferenceQueue的lock對象的wait方法直到被jvm喚醒)

何時調(diào)用finalize方法

??Finalizer類里定義了FinalizerThread,用于將ReferenceQueue里的對象取出并執(zhí)行finalize方法。具體代碼如下:


軟引用的具體回收時機可以參考:http://www.lxweimin.com/p/e46158238a77
參考文章:https://time.geekbang.org/column/article/6970
http://www.lxweimin.com/p/e46158238a77
http://www.lxweimin.com/p/7200da8b043f
https://mp.weixin.qq.com/s/fftHK8gZXHCXWpHxhPQpBg

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容