java大廠面試題整理(七)強/弱/軟/虛四大引用

這篇文章主要介紹四大引用,java中引用分四種:強引用,弱引用,軟引用,虛引用。
而在Java項目中,百分之九十五都是強引用,軟引用偶爾在緩存中用到,而弱和虛很少用到。下面我們一個個詳細的講一下:


關於引用的一些知識點我們可以在jdk8中文手冊中看一下:



下面一一說下四種引用:

強引用

當內存不足時,JVM開始垃圾回收,對於強引用的對象,就算是出現了OOM也不會對該對象進行回收,死都不收!
強引用是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還“活着”,垃圾收集器不會碰這種對象。在java中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態。它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到。JVM也不會回收。因此強引用是造成Java內存泄漏的主要原因之一。
對於一個普通的對象,如果沒有其它的引用關係,只要超過了引用的作用域或者顯式的將相應(強)引用賦值爲null,一般認爲就是可以被垃圾收集的了(當然了具體的回收時機還是要看垃圾收集策略)
一般我們常用的爲對象賦值就是強引用,下面用代碼寫個demo:

    public static void main(String[] args) throws Exception {
        Object o1 = new Object();
        Object o2 = o1;
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o1==o2);
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(o2);
    }

上面的代碼我們創建一個o1,然後又創建一個o2並且給其賦值o1.這個時候o1,o2指向一個對象。兩個也是全等的到這沒問題。
但是後來我們將o1賦值爲null。這個時候再輸出o1.o2,發現o1確實是null了,但是o2的指向還沒變。而到這個時候,因爲o2指向了這個對象,那麼哪怕爆發oom了,這個對象也不會被回收!


軟引用

什麼是軟引用?這個是比強引用弱化了一些的引用,需要用java.lang.ref.SoftReference類來實現。可以讓對象豁免一些垃圾回收。
強引用是死了崩了都不回收,而軟引用是內存足夠就不收,但是內存不夠了就會回收!
軟引用通常用在對內存敏感的程序中,比如高速緩存就有用到軟引用。內存夠用的時候就保留,不夠用就回收。
下面我們先去看看手冊中這個軟引用類如何使用:


這個手冊上介紹的比較清楚,先有這麼個對象,然後我們用這個類創建這個對象的軟引用。然後用get方法獲取這個引用的對象。下面我們用代碼試試:

/**
 * 
 * @author lisijia
 *
 */
public class SoftRenferenceDemo {
    
    public static void softE() {
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<Object>(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(softReference.get());
    }
    public static void softNE() {
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<Object>(o1);
        o1 = null;
        try {
            byte[] bytes = new byte[30*1024*1024];
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(o1);
            System.out.println(softReference.get());
        }
    }
    public static void main(String[] args) {
        softE();
        softNE();
    }

}

上面兩個方法第一種是測試內存夠用,第二種是測試內存不夠用。這裏需要注意!!!!第二種方式創建了一個30m左右的大對象。要想測試出效果要啓動的時候設置最大空間小於30m的。如下設置方式:


然後我們再運行上面的代碼:

注意代碼的執行:先執行夠用的那個方法,因爲內存夠用,所以哪怕o1設置爲null,其中虛引用指向的對象沒有被清除(同樣因爲內存夠用不會觸發gc,所以手動調用gc)。
而第二個方法因爲內存只有5m,但是創建一個30m的對象所以肯定是內存不夠用了。於是虛引用的指向也被清理了。所以是null。

弱引用

弱引用需要用java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短。
對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管JVM的內存空間是否足夠,都會回收該對象。
下面依然代碼測試:

    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<Object>(o1);
        System.out.println(o1);
        System.out.println(weakReference.get());
        o1 = null;
        System.gc();
        System.out.println(weakReference.get());
    }

運行結果是gc後weakReference.get()也是null。

什麼情景下需要軟引用和弱引用?
脫離實際場景談技術純屬耍流氓!那麼具體在什麼場景下使用軟引用或者弱引用呢?打個比方:有一個應用需要讀取大量的本地圖片。
如果每次讀取圖片都從硬盤中讀取會有嚴重的性能問題,但是一次性全部加載到內存中又可能造成內存溢出。
此時使用軟引用就可以解決這個問題。設計思路如下:
用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟/弱引用之間的映射關係。在內存不足時,JVM會自動回收這些緩存圖片對象所佔用的空間,從而有效的避免了OOM的問題。
WeakHashMap是什麼?
我們可以先去jdk文檔中查看:


然後下一步就是代碼測試,這裏我們用常用的HashMap和WeakHashMap對比測試:

    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<Integer, String>();
        Integer key = new Integer(1);
        String value = "1";
        map.put(key, value);
        System.out.println(map);
        key = null;
        System.gc();
        System.out.println(map);
        System.out.println("==================================================");
        WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<Integer, String>();
        Integer key1 = new Integer(2);
        String value1 = "2";
        weakHashMap.put(key1, value1);
        System.out.println(weakHashMap);
        key1 = null;
        System.gc();
        System.out.println(weakHashMap);
    }

首先正常來講上面的代碼除了map的類型不一樣其餘的代碼都是一樣的。然後運行結果是不同的,看圖:



注意一下這個結果weakHashMap經過gc以後變成了null。變成null的原因是這個key沒有了,weakHashMap中,存進去的key不是強引用而是弱引用。而這個強引用指向不在了,gc的時候弱引用是會被清理的。所以這個map的key被清理了,也就爲空了。

虛引用

虛引用是四大引用的最後一個。需要java.lang.ref.PhantomReference類來實現。
顧名思義,phantom是幽靈的意思,也就是形同虛設。與其他幾種引用都不同,虛引用並不會決定對象的生命週期。
如果一個對象僅持有虛引用,那麼他就和沒有任何引用一樣。在任何時候都可能被垃圾回收器回收。它不能單獨使用也不能通過它訪問對象。虛引用必須和引用隊列(ReferenceQueue)聯合使用。
虛引用的主要作用是跟蹤對象被垃圾回收的狀態。它僅僅是提供了一種確保對象被finalize以後,做某些事情的機制。
PhantomReference的get方法總是返回null,因此無法訪問對應的引用對象。其意義在於說明一個對象已經進入finalization階段,可以被gc回收,用來實現比finalization機制更加靈活的回收操作。
換句話說,設置虛引用關聯的唯一目的就是在這個對象被收集器回收的時候收到一個系統通知或者後續添加進一步的處理。
java技術允許使用finalize()方法在垃圾收集器將對象從內存中清出去之前做必要的清理工作。
下面依然是代碼展示(因爲虛引用本身獲取就是null,所以這裏用弱引用來測試):

    public static void main(String[] args) throws Exception{
        Object o1 = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
        WeakReference<Object> weakReference = new WeakReference<Object>(o1,queue);
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println(queue.poll());
        System.out.println("==========================");
        o1 = null;
        System.gc();
        TimeUnit.SECONDS.sleep(1l);
        System.out.println(o1);
        System.out.println(weakReference.get());        
        System.out.println(queue.poll());
    }

運行結果是最後一個隊列的輸出是有東西的,說明o1在被gc回收後,進入了隊列。


其實我們可以把弱引用換成虛引用,但是這樣只能確定被回收後隊列裏有東西了,get這步就看不到了。我偷個懶,上面代碼單純的把弱引用換成虛引用。如下圖:



其實這個引用隊列我們可以理解爲spring 的aop的後置通知,白話理解死之前做點什麼。

四大引用總結

強引用(默認使用):死了也不回收,哪怕oom。
軟引用:有空間就不收,沒空間就回收(地主家沒餘糧)。
弱引用:不管有沒有空間都回收。
虛引用:和回不回收不搭嘎。必須和引用隊列配合使用,只能死之前留個遺言。



上圖中,灰色部分都會被回收(其中有兩個互相強引用的是引用不可達)。

本篇筆記就記到這裏,主要講了四種引用的用法和區別。如果稍微幫到你了記得點個喜歡點個關注。也祝大家工作順順利利!

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