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

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