別再找了,一文完全解析Java 中的弱引用(參考官網)

概覽

本文會經過對弱引用的定義講起,而後經過案例的使用一步一步的深刻源碼進行分析其原理,從而讓讀者深入的理解什麼是弱引用,如何使用弱引用,什麼場景下會使用弱引用,弱引用能夠解決什麼樣的問題,以及它的源碼實現是怎樣的,其中會涉及的內存溢出,垃圾回收原理html

做用:

jdk 官網解釋:

弱引用主要應用在不阻止它的key或者value 被回收的mapping。直接貼英文吧,翻譯水平有限(weak references are for implementing canonicalizing mappings that do not prevent their keys (or values) from being reclaimed)java

我的理解:

弱引用的出現就是爲了垃圾回收服務的。它引用一個對象,可是並不阻止該對象被回收。若是使用一個強引用的話,只要該引用存在,那麼被引用的對象是不能被回收的。弱引用則沒有這個問題。在垃圾回收器運行的時候,若是一個對象的全部引用都是弱引用的話,該對象會被回收程序員

案例深度解析:

理想的狀況下,咱們但願當咱們再也不使用一個對象的時候,可以在gc 發生的時候就把它回收掉。可是有些時候,因爲咱們的粗忽,在壞的狀況下會致使內存溢出。這種案例尤爲發生在一個生命使用週期很長的map 存放了不少實際使用生命週期短的對象。請看下面這個例子面試

public class StrongRefenceDemo {


    static Map<String, String> map;

    public static void main(String[] args) throws Exception {

        StrongRefenceDemo demo = new StrongRefenceDemo();
        demo.strongTest();
        System.out.println("gc 發生前:" + map.size());
        System.out.println("開始通知GC");
        //注意,這裏只是經過垃圾回收器進行垃圾回收,並不必定立刻執行
        System.gc();
        Thread.sleep(1000 * 5);
        System.out.println("gc 發生後:" + map.size());

    }


    /** * 強引用測試 */
    public void strongTest() {
        map = new HashMap<>();
        String key = new String("key");
        String value = new String("value");
        map.put(key, value);
        key = null;
        value = null;
    }

}

複製代碼

運行後輸出結果:小程序

gc 發生前:1
開始通知GC
gc 發生後:1

複製代碼

從輸出的結果能夠看到,即便咱們經過把key和value 設置爲null 來告訴jvm,咱們再也不使用這個對象了,map 裏面對象依然沒有被GC 回收,由於key和value 被一個強引用map 指向,根據可達性判斷,垃圾回收器是不能回收掉key和value 這個對象的。map 被定義爲statis 的靜態變量,是一個使用生命週期很長的對象。在strongTest()方法中存在了一個key和value 的局部變量,它隨着方法的執行完,這個變量的生命使用週期就結束了,可是粗糙的程序員忘記remove 了,這個時候垃圾回收器是不能回收它的。若是這種生命週期相對短的對象不少,最終就有可能消耗掉JVM中所有的內存。api

可是這裏我有一個好奇,假如這裏的key和value 指向的對象在執行完strongTest()方法 之後用不着了,可是我可能又不是很好的判斷去主動調用remove 來移除它。想要垃圾回收器本身判斷回收掉可不能夠呢?答案實際上是能夠的,這個時候就是弱引用上場了,請看下面程序數組

public class WeakRefenceDemo {

    static Map<WeakReference<String>, String> map;

    public static void main(String[] args) throws Exception {

        WeakRefenceDemo demo = new WeakRefenceDemo();
        demo.weakTest();
        System.out.println("gc 發生前:" + map.size());
        System.out.println("開始通知GC");
        //注意,這裏只是經過垃圾回收器進行垃圾回收,並不必定立刻執行
        System.gc();
        Thread.sleep(1000 * 5);
        System.out.println("gc 發生後:" + map.size());


    }


    /** * 若引用測試 */
    public void weakTest() {
        map = new WeakHashMap<>();
        String key = new String("key");
        String value = new String("value");
        map.put(new WeakReference<>(key), value);
        key = null;
        value = null;
    }
}

複製代碼

運行上面代碼輸出結果安全

gc 發生前:1
開始通知GC
gc 發生後:0
複製代碼

從輸出結果0,咱們能夠判斷已經成功被垃圾回收了。what?整個過程咱們只是把HashMap 換成了WeakHashMap,而且key 由String 換成了WeakReference。其實就是因爲字符串只有弱引用指向,因此能夠被垃圾回收掉。是否是很簡單,若是到這裏你就中止研究弱引用了,那就太暴殄天物了bash

WeakHashMap 深度解析

上面的程序片斷中,其實只有key 設置了爲弱引用new WeakReference<>(key),那正常也就只有這個key 對應的內存被回收而已,因爲沒有調用remove ,裏面的value 和entry 也是不會回收掉的,那爲何最後輸出的size 是0 呢? 很好的問題,咱們深刻去看WeakHashMap 的源碼,咱們發現了一個神奇的方法expungeStaleEntries()。在看源碼以前先解析下引用隊列的概念: 在弱引用被回收的時候會把該對象放到引用隊列中,也就意味着從引用隊列中獲取的對象都是被回收的對象,先解釋到這裏,足以知足咱們下面的源碼分析了,接下來會作詳細的解析數據結構

/** * Expunges stale entries from the table. */
    private void expungeStaleEntries() {
    //這裏從引用隊列裏面取出一個已經被回收的對象
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                //下面就是經過遍歷鏈表來設置值爲null 來告訴垃圾回收器回收掉
                //注意WeakHashMap 和HashMap 的數據結構都是經過數組+鏈表組成的,只有理解了這點才知道下面的代碼作了什麼
                while (p != null) {
                    Entry<K,V> next = p.next;
                    //相等的時候,就意味着這個就是要回收的對象
                    if (p == e) {
                    //下面就是讓回收對象再也不被引用
                        if (prev == e)
                   
                            table[i] = next;
                        else
                        
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        //這裏經過設置value 爲null 來告訴垃圾回收
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
複製代碼

從上面的代碼片斷,大概的意思就是從引用隊列裏面取出被回收的對象,而後和WeakHashMap 中的對象查找,找到以後就把對應的value 也設置爲null,而且把對應的entry 設置爲null,來告訴GC 去回收它。從源碼能夠看到expungeStaleEntries() 這個方法在執行WeakHashMap中的任何方法的時候都會被調用到的

/** * Returns the table after first expunging stale entries. */
    private Entry<K,V>[] getTable() {
    //被調用
        expungeStaleEntries();
        return table;
    }
    
        /** * Returns the number of key-value mappings in this map. * This result is a snapshot, and may not reflect unprocessed * entries that will be removed before next attempted access * because they are no longer referenced. */
    public int size() {
        if (size == 0)
            return 0;
            //被調用
        expungeStaleEntries();
        return size;
    }
複製代碼

到這裏也就徹底明白爲何value 不設置爲弱引用和沒有顯性的調用remove 方法也能夠回收掉了

引用隊列

從上面的的源碼中,咱們大概知道了引用隊列的使用,那爲何要使用引用隊列呢?假如沒有引用隊列,上面的例子咱們就須要遍歷所有的元素一個一個的去找,若是數量少那還好,若是數量多的時候,確定就會出現一些性能問題。有了引用隊列那就輕鬆能夠解決上面的問題了。從WeakReference 源碼中咱們能夠看到有兩個構造函數,第二個是須要傳入引用隊列的

public WeakReference(T referent) {
       super(referent);
   }
   
   public WeakReference(T referent, ReferenceQueue<? super T> q) {
       super(referent, q);
   }
複製代碼

引用隊列hello word

Object referent = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

WeakReference weakReference1 = new WeakReference<>(referent);
WeakReference weakReference2 = new WeakReference<>(referent, referenceQueue);
複製代碼

使用中須要注意的細節: 因爲弱引用的對象在GC 發生的時候均可能會被回收掉,因此在使用以前咱們都須要判斷下是否爲null 來避免空指針異常

Object referent3 = weakReference2.get();
if (referent3 != null) {
    // GC hasn't removed the instance yet
} else {
    // GC has cleared the instance
}
複製代碼

總結

  1. 弱引用的出現是爲了垃圾回收的
  2. 一個對象只有弱引用指向它的時候,它是能夠被回收的
  3. 弱引用是在GC 發生的時候就進行回收,無論當時內存是否充足
  4. 若是你在建立弱引用指定一個引用隊列的話,弱引用對象被回收的時候,會把該對象放入引用隊列中
  5. 爲了安全使用,每次都要判斷下是否爲空來判斷該對象是否已經被回收,來避免空指針異常

看完兩件事

若是你以爲這篇內容對你挺有啓發,我想邀請你幫我2個小忙:

  1. 點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
  2. 關注公衆號「面試bat」,不按期分享原創知識,原創不易,請多支持(裏面還提供刷題小程序哦)。

jdk8官方文檔解析弱引用

相關文章
相關標籤/搜索