1. 程式人生 > >Java魔法堂:四種引用型別、ReferenceQueue和WeakHashMap

Java魔法堂:四種引用型別、ReferenceQueue和WeakHashMap

一、前言                            

  JDK1.2以前只提供一種引用型別——強引用 Object obj = new Object(); 。而JDK1.2後我們多另外的三個選擇分別是軟引用 java.lang.ref.SoftReference 、弱引用 java.lang.ref.WeakReference 和虛引用 java.lang.ref.PhantomReference 。下面將記錄對它們和相關連的引用佇列 java.lang.ref.ReferenceQueue 和 java.util.WeakHashMap 的學習筆記。

二、四種引用型別                        

  1. 強引用(Strong Reference)

     最常用的引用型別,如Object obj = new Object(); 。只要強引用存在則GC時則必定不被回收。

  2. 軟引用(Soft Reference)

     用於描述還游泳但非必須的物件,當堆將發生OOM(Out Of Memory)時則會回收軟引用所指向的記憶體空間,若回收後依然空間不足才會丟擲OOM。

     一般用於實現記憶體敏感的快取記憶體。

        示例:實現學生資訊查詢操作時有兩套資料操作的方案

                一、將得到的資訊存放在記憶體中,後續查詢則直接讀取記憶體資訊;(優點:讀取速度快;缺點:記憶體空間一直被佔,若資源訪問量不高,則浪費記憶體空間)

                二、每次查詢均從資料庫讀取,然後填充到TO返回。(優點:記憶體空間將被GC回收,不會一直被佔用;缺點:在GC發生之前已有的TO依然存在,但還是執行了一次資料庫查詢,浪費IO)

       通過軟引用解決:

ReferenceQueue q = new ReferenceQueue();

// 獲取資料並快取
Object obj = new Object();
SoftReference sr = new SoftReference(obj, q);

// 下次使用時
Object obj = (Object)sr.get();
if (obj == null
){ // 當軟引用被回收後才重新獲取 obj = new Object(); } // 清理被收回後剩下來的軟引用物件 SoftReference ref = null; while((ref = q.poll()) != null){ // 清理工作 }

  3. 弱引用(Weak Reference)

      發生GC時必定回收弱引用指向的記憶體空間。

  4. 虛引用(Phantom Reference)

      又稱為幽靈引用或幻影引用,,虛引用既不會影響物件的生命週期,也無法通過虛引用來獲取物件例項,僅用於在發生GC時接收一個系統通知。

  那現在問題來了,若一個物件的引用型別有多個,那到底如何判斷它的可達性呢?其實規則如下:

  1. 單條引用鏈的可達性以最弱的一個引用型別來決定;
  2. 多條引用鏈的可達性以最強的一個引用型別來決定;

      

     我們假設圖2中引用①和③為強引用,⑤為軟引用,⑦為弱引用,對於物件5按照這兩個判斷原則,路徑①-⑤取最弱的引用⑤,因此該路徑對物件5的引用為軟引用。同樣,③-⑦為弱引用。在這兩條路徑之間取最強的引用,於是物件5是一個軟可及物件(當將要發生OOM時則會被回收掉)。

  軟引用、弱引用和虛引用均為抽象類 java.lang.ref.Reference 的子類,而與引用佇列和GC相關的操作大多在抽象類Reference中實現。

三、引用佇列(java.lang.ref.ReferenceQueue)       

  引用佇列配合Reference的子類等使用,當引用物件所指向的記憶體空間被GC回收後,該引用物件則被追加到引用佇列的末尾(原始碼中 boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ 說明只供Reference例項呼叫,且僅能呼叫一次)。引用佇列有如下例項方法:

   Reference<? extends T> ReferenceQueue#poll() ,從佇列中出隊一個元素,若佇列為空則返回null。

   Reference<? extends T> ReferenceQueue#remove() ,從佇列中出隊一個元素,若沒有則阻塞直到有元素可出隊。

   Reference<? extends T> ReferenceQueue#remove(long timeout) ,從佇列中出隊一個元素,若沒有則阻塞直到有元素可出隊或超過timeout指定的毫秒數(由於採用wait(long timeout)方式實現等待,因此時間不能保證)。

四、 java.lang.ref.Reference                

   Reference內部通過一個 {Reference} next 的欄位來構建一個Reference型別的單向連結串列。另外其內部還包含一個 ReferenceQueue<? super T> queue 欄位存放引用物件對應的引用佇列,若Reference子類建構函式中沒有指定則使用ReferenceQueue.NULL,也就是說每個軟、弱、虛引用物件必定與一個引用佇列關聯。

   Reference還包含一個靜態欄位 {Reference} pending (預設為null),用於存放被GC回收了記憶體空間的引用物件單向連結串列。Reference通過靜態程式碼塊啟動一個優先順序最高的守護執行緒檢查pending欄位為null,若不為null則沿著單向連結串列將引用物件追加到該引用物件關聯的引用隊列當中(除非引用佇列為ReferenceQueue.NULL)。守護執行緒的原始碼如下:

    public void run() {
        for (;;) {

        Reference r;
        synchronized (lock) {
       // 檢查pending是否為null
if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try {
          // pending為null時,則將當前執行緒進入wait set,等待GC執行後執行notifyAll
lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } // 追加到對應的引用佇列中 ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } }

  注意:由於通過靜態程式碼塊進行執行緒的建立和啟動,因此Reference的所有子類例項均通過同一個執行緒進行向各自的引用佇列追加引用物件的操作。

五、java.util.WeakHashMap                 

  由於WeakHashMap的鍵物件為弱引用,因此當發生GC時鍵物件所指向的記憶體空間將被回收,被回收後再呼叫size、clear或put等直接或間接呼叫私有expungeStaleEntries方法的例項方法時,則這些鍵物件已被回收的專案(Entry)將被移除出鍵值對集合中。

  下列程式碼將發生OOM

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

        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

        for (int i = 0; i < 1000; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[1000][1000], new byte[1000][1000]);
            maps.add(d);
            System.gc();
            System.err.println(i);
        }
    }

  而下面的程式碼因為集合的Entry被移除因此不會發生OOM

public static void main(String[] args) throws Exception {  
  
        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();  
  
        for (int i = 0; i < 1000; i++) {  
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();  
            d.put(new byte[1000][1000], new byte[1000][1000]);  
            maps.add(d);  
            System.gc();  
            System.err.println(i);  
  
            for (int j = 0; j < i; j++) {
// 觸發移除Entry操作 System.err.println(j
+ " size" + maps.get(j).size()); } } }

六、總結                            

七、參考                            

《WeakHashMap的神話》http://www.javaeye.com/topic/587995

http://hongjiang.info/java-referencequeue/