WeakHashMap原始碼探討(基於JDK1.8)
阿新 • • 發佈:2019-01-05
WeakHashMap簡介
WeakHashMap跟普通的HashMap不同,WeakHashMap的行為一定程度上基於垃圾收集器的行為,因此一些Map資料結構對應的常識在WeakHashMap上會失效——size()方法的返回值會隨著程式的執行變小,isEmpty()方法的返回值會從false變成true等等。
WeakHashMap的繼承結構
,它儲存的內容也是鍵值對(key-value)對映,而且鍵和值都可以是null。
這個“弱鍵”的原理呢?大致上就是,通過WeakReference和ReferenceQueue實現的。 關於Java中的引用型別 引用型別主要分為4種: ①強引用(Strong Reference)就是永遠不會回收掉被引用的物件,比如說我們程式碼中new出來的物件。 ②軟引用(SoftReference)表示有用但是非必需的,如果系統記憶體資源緊張,可能就會被回收。 ③弱引用(WeakReference)表示非必需的物件,只能存活到下一次垃圾回收發生之前。 ④虛引用(PhantomReference)是最弱的,這個引用無法操作物件。 WeakHashMap中用WeakReference,也就是虛引用來實現。
關於引用佇列ReferenceQueue 引用佇列相當於一個電話簿一樣的東西,用於監聽和管理在引用物件中被回收的物件。 幫助理解ReferenceQueue 的demo
WeakHashMap中ReferenceQueue如何幫助實現弱鍵? ReferenceQueue是一個佇列,它會儲存被GC回收的“弱鍵”。實現步驟是: (01) 新建WeakHashMap,將“鍵值對”新增到WeakHashMap中。 實際上,WeakHashMap是通過陣列table儲存Entry(鍵值對);每一個Entry實際上是一個單向連結串列,即Entry是鍵值對連結串列。 (02) 當某“弱鍵”不再被其它物件引用,並被GC回收時。在GC回收該“弱鍵”時,這個“弱鍵”也同時會被新增到ReferenceQueue(queue)佇列中。 (03) 當下一次我們需要操作WeakHashMap時,會先同步table和queue。table中儲存了全部的鍵值對,而queue中儲存被GC回收的鍵值對;同步它們,就是刪除table中被GC回收的鍵值對。 這就是“弱鍵”如何被自動從WeakHashMap中刪除的步驟了。 WeakHashMap實現demo
WeakHashMap的繼承結構
WeakHashMap中重要的成員變數
publicclassWeakHashMap<K,V>
extendsAbstractMap<K,V>
implementsMap<K,V>{
WeakHashMap實際上是用Entry陣列來儲存資料的int modCount; //用來幫助實現fail-fast機制
Entry<K,V>[] table; //底層採用陣列+單向連結串列
privateint size; //儲存的節點數
privateint threshold; //size超過此閥值時,需要resize,擴容2倍
privatefinalfloat loadFactor; //負載因子
privatefinalReferenceQueue<Object> queue =newReferenceQueue<>(); //儲存的是“已被GC清除”的“弱引用的鍵”
Entry類的結構如下:
Entry<K,V>[] table;
WeakHashMap的鍵是“弱鍵”。在 WeakHashMap 中,當某個鍵不再正常使用時,會被從WeakHashMap中被自動移除。更精確地說,對於一個給定的鍵,其對映的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,然後被終止並回收。某個鍵被終止時,它對應的鍵值對也就從對映中有效地移除了。
privatestaticclassEntry<K,V>extendsWeakReference<Object>implementsMap.Entry<K,V>
這個“弱鍵”的原理呢?大致上就是,通過WeakReference和ReferenceQueue實現的。 關於Java中的引用型別 引用型別主要分為4種: ①強引用(Strong Reference)就是永遠不會回收掉被引用的物件,比如說我們程式碼中new出來的物件。 ②軟引用(SoftReference)表示有用但是非必需的,如果系統記憶體資源緊張,可能就會被回收。 ③弱引用(WeakReference)表示非必需的物件,只能存活到下一次垃圾回收發生之前。 ④虛引用(PhantomReference)是最弱的,這個引用無法操作物件。 WeakHashMap中用WeakReference,也就是虛引用來實現。
關於引用佇列ReferenceQueue 引用佇列相當於一個電話簿一樣的東西,用於監聽和管理在引用物件中被回收的物件。 幫助理解ReferenceQueue 的demo
擷取輸出: ...... 85回收了[email protected] map的size = 100 86回收了[email protected] map的size = 100 87回收了[email protected] map的size = 100 88回收了[email protected] map的size = 100 89回收了[email protected] map的size = 100 90回收了[email protected] map的size = 100 梳理一下執行流程: 1、bytes物件存入到weakReference物件中。 2、weakReference物件作為key,一個Object作為值存入HashMap中 2、GC回收了bytes物件,這個時候就要把引用這個物件的weakReference物件儲存到ReferenceQueue中 3、死迴圈ReferenceQueue, 打印出被回收的物件。 在這裡,jvm回收了弱引用bytes,但沒有回收作為map中的key的weakReference,因此每次我們列印的size都是100。 上面這個邏輯就是核心WeakHashMap的實現,WeakHashMap只不過比上述的程式碼多了一步:把引用回收的物件從Map中移除罷了。
publicclassReferenceTest{
privatestaticfinalint _1MB =1024*1024;//設定大小為1MB
publicstaticvoid main(String[] args)throwsInterruptedException{
ReferenceQueue<Object> referenceQueue =newReferenceQueue<Object>();//用引用佇列進行監控引用的回收情況
Object value =newObject();
Map<Object,Object> map =newHashMap<Object,Object>();
for(int i =0; i <100; i++){//迴圈100次把資料插入到弱應用中(WeakReference), 同時把弱引用作為key存入HashMap
byte[] bytes =newbyte[_1MB];
//每個引用中都有關聯引用佇列(referenceQueue)的構造器,用引用佇列監聽回收情況
//如此,那麼每次WeakReference中的bytes被回收之後,那麼這個weakReference物件就會放入引用佇列
WeakReference<byte[]> weakReference =newWeakReference<byte[]>(bytes, referenceQueue);
map.put(weakReference, value);
}
Thread thread =newThread(newRunnable(){//執行緒通過呼叫引用佇列的情況檢視那些物件被回收
@SuppressWarnings("unchecked")
publicvoid run(){
try{
int cnt =0;
WeakReference<byte[]> k;
while((k =(WeakReference<byte[]>) referenceQueue.remove())!=null){//返回被回收物件的引用(注意本例中被回收的是bytes)
System.out.println((cnt++)+"回收了"+k);
System.out.println("map的size = "+ map.size());//用於監控map的儲存數量有沒有發生變化
}
}catch(Exception e){
// TODO: handle exception
}
}
});
thread.start();
}
}
WeakHashMap中ReferenceQueue如何幫助實現弱鍵? ReferenceQueue是一個佇列,它會儲存被GC回收的“弱鍵”。實現步驟是: (01) 新建WeakHashMap,將“鍵值對”新增到WeakHashMap中。 實際上,WeakHashMap是通過陣列table儲存Entry(鍵值對);每一個Entry實際上是一個單向連結串列,即Entry是鍵值對連結串列。 (02) 當某“弱鍵”不再被其它物件引用,並被GC回收時。在GC回收該“弱鍵”時,這個“弱鍵”也同時會被新增到ReferenceQueue(queue)佇列中。 (03) 當下一次我們需要操作WeakHashMap時,會先同步table和queue。table中儲存了全部的鍵值對,而queue中儲存被GC回收的鍵值對;同步它們,就是刪除table中被GC回收的鍵值對。 這就是“弱鍵”如何被自動從WeakHashMap中刪除的步驟了。 WeakHashMap實現demo
執行結果: 9 0 0 0 ....... 其實當我們在迴圈100次新增資料時,就已經開始回收弱引用了,因此我們會看到第一次列印的size是9,而不是100,在列印了大小之後,建議系統(System.gc())發起一次GC操作,為什麼說是建議呢?因為系統不一定會接收到你指令就會發生GC的。一旦GC發生,那麼弱引用就會被清除,導致WeakHashMap的大小為0。 同時,值得一提的是,存在WeakHashMap中的資料,並不會平白無故就給你移除了map中的資料,必然是你觸發了一些操作,在上述程式碼中size方法就會觸發這個操作。
publicclassWeakTest{
privatestaticfinalint _1MB =1024*1024;//設定大小為1MB
publicstaticvoid main(String[] args)throwsInterruptedException{
Object value =newObject();
WeakHashMap<Object,Object> map =newWeakHashMap<Object,Object>();
for(int i =0; i <100; i++){//迴圈100次把資料插入WeakHashMap中
byte[] bytes =newbyte[_1MB];
map.put(bytes, value);
}
while(true){//死迴圈監控map大小變化
Thread.sleep(500);//稍稍停頓,效果更直觀
System.out.println(map.size());//列印WeakHashMap的大小
System.gc();//建議系統進行GC
}
}
}
publicint size(){
if(size ==0)
return0;
expungeStaleEntries();
return size;
}
privatevoid expungeStaleEntries(){
for(Object x;(x = queue.poll())!=null;){
syn