何爲ReferenceQueue
在java的引用體系中,存在着強引用,軟引用,虛引用,幽靈引用,這4種引用類型。在正常的使用過程中,我們定義的類型都是強引用的,這種引用類型在回收中,只有當其它對象沒有對這個對象的引用時,纔會被GC回收掉。簡單來說,對於以下定義:
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中獲取到相應的對象信息,同時進行額外的處理。比如反向操作,數據清理等。
使用隊列進行數據監控
一個簡單的例子,通過往map中放入10000個對象,每個對象大小爲1M字節數組。使用引用隊列監控被放入的key的回收情況。代碼如下所示:
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的數據,代碼如下:
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();
結果如下所示:
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就是這樣的一個類似實現。
在類weakHashMap中的使用
weakHashMap即使用weakReference當作key來進行數據的存儲,當key中的引用被gc掉之後,它會自動(類似自動)的方式將相應的entry給移除掉,即我們會看到size發生了變化。
從簡單來看,我們認爲其中所有一個類似的機制從queue中獲取引用信息,從而使得被gc掉的key值所對應的entry從map中被移除。這個處理點就在我們調用weakhashmap的各個處理點中,比如get,size,put等。簡單點來說,就是在調用get時,weakHashMap會先處理被gc掉的key值,然後再處理我們的業務調用。
簡單點代碼如下:
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,最終的輸出類似如下所示:
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。
隊列監控的反向操作
反向操作,即意味着一個數據變化了,可以通過weakReference對象反向拿相關的數據,從而進行業務的處理。比如,我們可以通過繼承weakReference對象,加入自定義的字段值,額外處理。一個類似weakHashMap如下,這時,我們不再將key值作爲弱引用處理,而是封裝在weakReference對象中,以實現額外的處理。
WeakR對象定義如下:
//描述一種強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進行存儲,如下所示:
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對象進行了額外的處理,如下所示:
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映射。
相應的輸出,如下所示:
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
在Google Guava的簡單描述
在google guava中,實現了一個類似在第4中所對應的操作。同時對於反向操作,通過繼承一個指定的對象(可以理解爲weakReference和callback的組合對象),當value值被gc之後,即可以直接在回調中處理業務即可,不需要自己來監控queue。(見FinalizableReference)
原文地址:https://www.iflym.com/index.php/java-programe/201407140001.html