ReferenceQueue的使用


轉:http://www.iflym.com/index.php/java-programe/201407140001.html

1 何為ReferenceQueue

在java的引用體系中,存在着強引用,軟引用,虛引用,幽靈引用,這4種引用類型。在正常的使用過程中,我們定義的類型都是強引用的,這種引用類型在回收中,只有當其它對象沒有對這個對象的引用時,才會被GC回收掉。簡單來說,對於以下定義:

1
2
Object obj = new Object();
Ref ref = new Ref(obj);

在這種情況下,如果ref沒有被GC,那么obj這個對象肯定不會GC的。因為ref引用到了obj。如果obj是一個大對象呢,多個這種對象的話,應用肯定一會就掛掉了。

那么,如果我們希望在這個體系中,如果obj沒有被其它對象引用,只是在這個Ref中存在引用時,就把obj對象gc掉。這時候就可以使用這里提到的Reference對象了。

我們希望當一個對象被gc掉的時候通知用戶線程,進行額外的處理時,就需要使用引用隊列了。ReferenceQueue即這樣的一個對象,當一個obj被gc掉之后,其相應的包裝類,即ref對象會被放入queue中。我們可以從queue中獲取到相應的對象信息,同時進行額外的處理。比如反向操作,數據清理等。

2 使用隊列進行數據監控

一個簡單的例子,通過往map中放入10000個對象,每個對象大小為1M字節數組。使用引用隊列監控被放入的key的回收情況。代碼如下所示:

 

1
2
3
4
5
6
7
8
Object value = new Object();
Map<Object, Object> map = new HashMap<>();
for ( int i = 0 ;i < 10000 ;i++) {
     byte [] bytes = new byte [_1M];
     WeakReference< byte []> weakReference = new WeakReference< byte []>(bytes, referenceQueue);
     map.put(weakReference, value);
}
System.out.println( "map.size->" + map.size());

這里使用了weakReference對象,即當值不再被引用時,相應的數據被回收。另外使用一個線程不斷地從隊列中獲取被gc的數據,代碼如下:

01
02
03
04
05
06
07
08
09
10
11
12
13
Thread thread = new Thread(() -> {
     try {
         int cnt = 0 ;
         WeakReference< byte []> k;
         while ((k = (WeakReference) referenceQueue.remove()) != null ) {
             System.out.println((cnt++) + "回收了:" + k);
         }
     } catch (InterruptedException e) {
         //結束循環
     }
});
thread.setDaemon( true );
thread.start();

結果如下所示:

1
2
3
4
5
9992回收了:java.lang.ref.WeakReference@1d13cd4
9993回收了:java.lang.ref.WeakReference@118b73a
9994回收了:java.lang.ref.WeakReference@1865933
9995回收了:java.lang.ref.WeakReference@ad82c
map.size->10000

在這次處理中,map並沒有因為不斷加入的1M對象由產生OOM異常,並且最終運行結果之后map中的確有1萬個對象。表示確實被放入了相應的對象信息。不過其中的key(即weakReference)對象中的byte[]對象卻被回收了。即不斷new出來的1M數組被gc掉了。

從命令行中,我們看到有9995個對象被gc,即意味着在map的key中,除了weakReference之外,沒有我們想要的業務對象。那么在這樣的情況下,是否意味着這9995個entry,我們認為就是沒有任何意義的對象,那么是否可以將其移除掉呢。同時還期望size值可以打印出5,而不是10000.
WeakHashMap就是這樣的一個類似實現。

3 在類weakHashMap中的使用

weakHashMap即使用weakReference當作key來進行數據的存儲,當key中的引用被gc掉之后,它會自動(類似自動)的方式將相應的entry給移除掉,即我們會看到size發生了變化。

從簡單來看,我們認為其中所有一個類似的機制從queue中獲取引用信息,從而使得被gc掉的key值所對應的entry從map中被移除。這個處理點就在我們調用weakhashmap的各個處理點中,比如get,size,put等。簡單點來說,就是在調用get時,weakHashMap會先處理被gc掉的key值,然后再處理我們的業務調用。

簡單點代碼如下:

1
2
3
4
5
6
public int size() {
     if (size == 0 )
         return 0 ;
     expungeStaleEntries();
     return size;
}

此處的expungeStaleEntries即移除方法,具體的邏輯可以由以下的流程來描述:

  • A:使用一個繼承於WeakReference的entry對象表示每一個kv對,其中的原引用對象即我們在放入map中的key值
  • B:為保證效率以及盡可能的不使用key值,hash經過預先計算。這樣在定位數據及重新get時不再需要使用原引用對象
  • C:由queue拿到的事件對象,即這里的entry值。通過entry定位到具體的桶位置,通過鏈表計算將entry的前后重新連接起來(即p.pre.next = p.next)

因此,這里的引用處理並不是自動的,其實是我們在調用某些方法的時候處理,所以我們認為它不是一種自動的,只是表面上看起來是這種處理。
具體的代碼,即將開始的map定義為一個WeakHashMap,最終的輸出類似如下所示:

1
2
3
4
9993回收了:java.lang.ref.WeakReference@12aa816
9994回收了:java.lang.ref.WeakReference@2bd967
9995回收了:java.lang.ref.WeakReference@13e9593
weakHashMap.size->4

在上面的代碼中,由於weakhashmap不允許自定義queue,所以上面的監控是針對value的。在weakHashMap中,queue在weakhashmap在內部定義,並且由內部消化使用了。如果我們在自己進一步處理,那就只能自定義類似weakHashMap實現,或者使用反向操作。即在監控到變化之后,自己處理map的kv。

4 隊列監控的反向操作

反向操作,即意味着一個數據變化了,可以通過weakReference對象反向拿相關的數據,從而進行業務的處理。比如,我們可以通過繼承weakReference對象,加入自定義的字段值,額外處理。一個類似weakHashMap如下,這時,我們不再將key值作為弱引用處理,而是封裝在weakReference對象中,以實現額外的處理。

WeakR對象定義如下:

1
2
3
4
5
6
7
8
9
//描述一種強key關系的處理,當value值被回收之后,我們可以通過反向引用將key從map中移除的做法
//即通過在weakReference中加入其所引用的key值,以獲取key信息,再反向移除map信息
class WeakR extends WeakReference< byte []> {
     private Object key;
     WeakR(Object key, byte [] referent, ReferenceQueue<? super byte []> q) {
         super (referent, q);
         this .key = key;
     }
}

那么,相應的map,我們就使用普通的hashMap,將weakR作為value進行存儲,如下所示:

1
2
3
4
5
6
final Map<Object, WeakR> hashMap = new HashMap<>();
for ( int i = 0 ;i < 10000 ;i++) {
             byte [] bytesKey = new byte [_1M];
             byte [] bytesValue = new byte [_1M];
             hashMap.put(bytesKey, new WeakR(bytesKey, bytesValue, referenceQueue));
         }

相應的隊列,我們則一樣地進行監控,不同的是,我們對獲取的WeakR對象進行了額外的處理,如下所示:

1
2
3
4
5
6
7
8
int cnt = 0 ;
                     WeakR k;
                     while ((k = (WeakR) referenceQueue.remove()) != null ) {
                         System.out.println((cnt++) + "回收了:" + k);
                         //觸發反向hash remove
                         hashMap.remove(k.key);
                         //額外對key對象作其它處理,比如關閉流,通知操作等
                     }

其實就是拿到反向引用的key值(這里的value已經不存在了),因為kv映射已沒有意義,將其從map中移除掉。同時,我們還可以作其它的操作(具體的操作還沒想到,嘿嘿)

這個也可以理解為就是一個類似cache的實現。
在cache中,key不重要並且通常都很少,value才是需要對待的。這里通過監控value變化,反向修改map,以達到控制kv的目的,避免出現無用的kv映射。

相應的輸出,如下所示:

1
2
3
4
9995回收了:com.m_ylf.study.java.reference.TestCase$1WeakR@13c5f83
9996回收了:com.m_ylf.study.java.reference.TestCase$1WeakR@197558c
9997回收了:com.m_ylf.study.java.reference.TestCase$1WeakR@164bc7e
hashMap.size->1

5 在google guava的簡單描述

在google guava中,實現了一個類似在第4中所對應的操作。同時對於反向操作,通過繼承一個指定的對象(可以理解為weakReference和callback的組合對象),當value值被gc之后,即可以直接在回調中處理業務即可,不需要自己來監控queue。(見FinalizableReference)


免責聲明!

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



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