1. 程式人生 > >WeakHashMap原始碼探討(基於JDK1.8)

WeakHashMap原始碼探討(基於JDK1.8)

WeakHashMap簡介 WeakHashMap跟普通的HashMap不同,WeakHashMap的行為一定程度上基於垃圾收集器的行為,因此一些Map資料結構對應的常識在WeakHashMap上會失效——size()方法的返回值會隨著程式的執行變小,isEmpty()方法的返回值會從false變成true等等。
WeakHashMap的繼承結構
  1. publicclassWeakHashMap<K,V>
  2. extendsAbstractMap<K,V>
  3. implementsMap<K,V>{
WeakHashMap中重要的成員變數
  1. Entry<K,V>[] table; //底層採用陣列+單向連結串列
  2. privateint size; //儲存的節點數
  3. privateint threshold; //size超過此閥值時,需要resize,擴容2倍
  4. privatefinalfloat loadFactor; //負載因子
  5. privatefinalReferenceQueue<Object> queue =newReferenceQueue<>(); //儲存的是“已被GC清除”的“弱引用的鍵”
           int modCount; //用來幫助實現fail-fast機制
WeakHashMap實際上是用Entry陣列來儲存資料的
,它儲存的內容也是鍵值對(key-value)對映,而且鍵和值都可以是null。
  1. Entry<K,V>[] table;
Entry類的結構如下:
  1. privatestaticclassEntry<K,V>extendsWeakReference<Object>implementsMap.Entry<K,V>
WeakHashMap的鍵是“弱鍵”。在 WeakHashMap 中,當某個鍵不再正常使用時,會被從WeakHashMap中被自動移除。更精確地說,對於一個給定的鍵,其對映的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,然後被終止並回收。某個鍵被終止時,它對應的鍵值對也就從對映中有效地移除了。

這個“弱鍵”的原理呢?大致上就是,通過WeakReference和ReferenceQueue實現的。  關於Java中的引用型別 引用型別主要分為4種: 強引用(Strong Reference)就是永遠不會回收掉被引用的物件,比如說我們程式碼中new出來的物件。 軟引用(SoftReference)表示有用但是非必需的,如果系統記憶體資源緊張,可能就會被回收。 弱引用(WeakReference)表示非必需的物件,只能存活到下一次垃圾回收發生之前。 虛引用(PhantomReference)是最弱的,這個引用無法操作物件。 WeakHashMap中用WeakReference,也就是虛引用來實現。
關於引用佇列ReferenceQueue  引用佇列相當於一個電話簿一樣的東西,用於監聽和管理在引用物件中被回收的物件。 幫助理解ReferenceQueue 的demo
  1. publicclassReferenceTest{
  2. privatestaticfinalint _1MB =1024*1024;//設定大小為1MB
  3. publicstaticvoid main(String[] args)throwsInterruptedException{
  4. ReferenceQueue<Object> referenceQueue =newReferenceQueue<Object>();//用引用佇列進行監控引用的回收情況
  5. Object value =newObject();
  6. Map<Object,Object> map =newHashMap<Object,Object>();
  7. for(int i =0; i <100; i++){//迴圈100次把資料插入到弱應用中(WeakReference), 同時把弱引用作為key存入HashMap
  8. byte[] bytes =newbyte[_1MB];
  9. //每個引用中都有關聯引用佇列(referenceQueue)的構造器,用引用佇列監聽回收情況
  10. //如此,那麼每次WeakReference中的bytes被回收之後,那麼這個weakReference物件就會放入引用佇列
  11. WeakReference<byte[]> weakReference =newWeakReference<byte[]>(bytes, referenceQueue);
  12. map.put(weakReference, value);
  13. }
  14. Thread thread =newThread(newRunnable(){//執行緒通過呼叫引用佇列的情況檢視那些物件被回收
  15. @SuppressWarnings("unchecked")
  16. publicvoid run(){
  17. try{
  18. int cnt =0;
  19. WeakReference<byte[]> k;
  20. while((k =(WeakReference<byte[]>) referenceQueue.remove())!=null){//返回被回收物件的引用(注意本例中被回收的是bytes)
  21. System.out.println((cnt++)+"回收了"+k);
  22. System.out.println("map的size = "+ map.size());//用於監控map的儲存數量有沒有發生變化
  23. }
  24. }catch(Exception e){
  25. // TODO: handle exception
  26. }
  27. }
  28. });
  29. thread.start();
  30. }
  31. }
擷取輸出: ...... 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中移除罷了。
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
  1. publicclassWeakTest{
  2. privatestaticfinalint _1MB =1024*1024;//設定大小為1MB
  3. publicstaticvoid main(String[] args)throwsInterruptedException{
  4. Object value =newObject();
  5. WeakHashMap<Object,Object> map =newWeakHashMap<Object,Object>();
  6. for(int i =0; i <100; i++){//迴圈100次把資料插入WeakHashMap中
  7. byte[] bytes =newbyte[_1MB];
  8. map.put(bytes, value);
  9. }
  10. while(true){//死迴圈監控map大小變化
  11. Thread.sleep(500);//稍稍停頓,效果更直觀
  12. System.out.println(map.size());//列印WeakHashMap的大小
  13. System.gc();//建議系統進行GC
  14. }
  15. }
  16. }
執行結果: 9 0 0 0 ....... 其實當我們在迴圈100次新增資料時,就已經開始回收弱引用了,因此我們會看到第一次列印的size是9,而不是100,在列印了大小之後,建議系統(System.gc())發起一次GC操作,為什麼說是建議呢?因為系統不一定會接收到你指令就會發生GC的。一旦GC發生,那麼弱引用就會被清除,導致WeakHashMap的大小為0。 同時,值得一提的是,存在WeakHashMap中的資料,並不會平白無故就給你移除了map中的資料,必然是你觸發了一些操作,在上述程式碼中size方法就會觸發這個操作。
  1. publicint size(){
  2. if(size ==0)
  3. return0;
  4. expungeStaleEntries();
  5. return size;
  6. }
  1. privatevoid expungeStaleEntries(){
  2. for(Object x;(x = queue.poll())!=null;){
  3. syn