Java的強引用、弱引用、軟引用和虛引用,以及用途


Java種除了基本數據類型,其它數據類型都是引用的數據類型。而應用數據類型根據生命周期的長短又分為:強引用、弱引用、軟引用和需引用(幻象引用),我們平時基本上只用到強引用類型,而其他的引用類型我們也就在面試中,或者平日閱讀類庫或其他框架源碼的時候才能見到。

1.強引用

用到的new了一個對象就是強引用,例如 Object obj = new Object();

當JVM的內存空間不足時,寧願拋出OutOfMemoryError使得程序異常終止也不會回收具有強引用的存活着的對象!

強引用對象回收:

當一個普通對象沒有其他引用關系,只要超過了引用的作用域或者顯示的將引用賦值為null時,你的對象就表明不是存活着,這樣就會可以被GC回收了。當然回收的時間是不一定的具體得看GC回收策略。

2.軟引用

軟引用的生命周期比強引用短一些。軟引用是通過SoftReference類實現的。

Object obj = new Object();
//SoftReference softObj = new SoftReference(obj);//轉換為軟引用

ReferenceQueue qu = new ReferenceQueue()
SoftReference softObj = new SoftReference(obj, qu);
obj = null; //去除強引用

  這樣就是一個簡單的軟引用使用方法。當JVM認為內存空間不足時,就回去試圖回收軟引用指向的對象,也就是說在JVM拋出OutOfMemoryError之前,會去清理軟引用對象。軟引用可以與引用隊列(ReferenceQueue)聯合使當softObj軟引用的obj被GC回收之后,softObj 對象就會被塞到queue中,之后我們可以通過這個隊列的poll()來檢查你關心的對象是否被回收了,如果隊列為空,就返回一個null。反之就返回軟引用對象也就是softObj。軟引用一般用來實現內存敏感的緩存,如果有空閑內存就可以保留緩存,當內存不足時就清理掉,這樣就保證使用緩存的同時不會耗盡內存。例如圖片緩存框架中緩存圖片就是通過軟引用的。

用途:比如考慮一個圖像編輯器的程序。該程序會把圖像文件的全部內容都讀取到內存中,以方便進行處理。而用戶也可以同時打開多個文件。當同時打開的文件過多的時候,就可能造成內存不足。如果使用軟引用來指向圖像文件內容的話,垃圾回收器就可以在必要的時候回收掉這些內存。

3.弱引用

Object obj = new Object();
WeakReference<Object>  weakObj = new WeakReference<Object>(obj);
obj = null;

  

弱引用是通過WeakReference類實現的,它的生命周期比軟引用還要短,也是通過get()方法獲取對象。在GC的時候,不管內存空間足不足都會回收這個對象,同樣也可以配合ReferenceQueue 使用,也同樣適用於內存敏感的緩存。ThreadLocal中的key就用到了弱引用。

用途:

弱引用的作用在於解決強引用所帶來的對象之間在存活時間上的耦合關系。弱引用最常見的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對象作為鍵來使用。當一個鍵值對被放入到哈希表中之后,哈希表對象本身就有了對這些鍵和值對象的引用。如果這種引用是強引用的話,那么只要哈希表對象本身還存活,其中所包含的鍵和值對象是不會被回收的。如果某個存活時間很長的哈希表中包含的鍵值對很多,最終就有可能消耗掉JVM中全部的內存。

  對於這種情況的解決辦法就是使用弱引用來引用這些對象,這樣哈希表中的鍵和值對象都能被垃圾回收。Java中提供了WeakHashMap來滿足這一常見需求。

4.幻象引用

也稱虛引用,是通過PhantomReference類實現的。任何時候可能被GC回收,就像沒有引用一樣。無法通過虛引用訪問對象的任何屬性或者函數。那就要問了要它有什么用?虛引用僅僅只是提供了一種確保對象被finalize以后來做某些事情的機制。比如說這個對象被回收之后發一個系統通知啊啥的。虛引用是必須配合ReferenceQueue 使用的,具體使用方法和上面提到軟引用的一樣。主要用來跟蹤對象被垃圾回收的活動。

幽靈引用及其隊列的使用情況並不多見,主要用來實現比較精細的內存使用控制,這對於移動設備來說是很有意義的。程序可以在確定一個對象要被回收之后,再申請內存創建新的對象。通過這種方式可以使得程序所消耗的內存維持在一個相對較低的數量。比如下面的代碼給出了一個緩沖區的實現示例。

  private byte[] data = new byte[0];
    private ReferenceQueue<byte[]> queue = new ReferenceQueue<byte[]>();
    private PhantomReference<byte[]> ref = new PhantomReference<byte[]>(data, queue);
    public byte[] get(int size) {
        if (size <= 0) {
            throw new IllegalArgumentException("Wrong buffer size");
        }
        if (data.length < size) {
            data = null;
            System.gc(); //強制運行垃圾回收器
             try {
                queue.remove(); //該方法會阻塞直到隊列非空
                ref.clear(); //幽靈引用不會自動清空,要手動運行
                ref = null;
                data = new byte[size];
                ref = new PhantomReference<byte[]>(data, queue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
       }
       return data;
    }
}
 

  在上面的代碼中,每次申請新的緩沖區的時候,都首先確保之前的緩沖區的字節數組已經被成功回收。引用隊列的remove方法會阻塞直到新的幽靈引用被加入到隊列中。不過需要注意的是,這種做法會導致垃圾回收器被運行的次數過多,可能會造成程序的吞吐量過低。

 

部分引用:https://baijiahao.baidu.com/s?id=1629253892215446066&wfr=spider&for=pc


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM