四種引用類型、ReferenceQueue和WeakHashMap

1、前言                            html

  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 的學習筆記。java

 

2、四種引用類型                        數據庫

  1. 強引用(Strong Reference)緩存

     最經常使用的引用類型,如Object obj = new Object(); 。只要強引用存在則GC時則一定不被回收。函數

  2. 軟引用(Soft Reference)學習

     用於描述還游泳但非必須的對象,當堆將發生OOM(Out Of Memory)時則會回收軟引用所指向的內存空間,若回收後依然空間不足纔會拋出OOM。spa

     通常用於實現內存敏感的高速緩存。線程

        示例:實現學生信息查詢操做時有兩套數據操做的方案htm

                1、將獲得的信息存放在內存中,後續查詢則直接讀取內存信息;(優勢:讀取速度快;缺點:內存空間一直被佔,若資源訪問量不高,則浪費內存空間)對象

                2、每次查詢均從數據庫讀取,而後填充到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中實現。

 

3、引用隊列(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)方式實現等待,所以時間不能保證)。

  

4、 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的全部子類實例均經過同一個線程進行向各自的引用隊列追加引用對象的操做。

 

5、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());  
            }  
        }  
    }  

複製代碼

 

6、總結                            

  尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/4268411.html  ^_^肥仔John

 

7、參考                            

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

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

相關文章
相關標籤/搜索