文章出處

一、前言                            

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

 

六、總結                            

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

 

七、參考                            

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

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

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()