【轉載】 深入理解WeakHashmap
WeakHashmap
(一) 檢視API文件,WeakHashmap要點如下:
以弱鍵 實現的基於雜湊表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。更精確地說,對於一個給定的鍵,其對映的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,然後被回收。丟棄某個鍵時,其條目從對映中有效地移除
WeakHashMap 類的行為部分取決於垃圾回收器的動作。因為垃圾回收器在任何時候都可能丟棄鍵,WeakHashMap 就像是一個被悄悄移除條目的未知執行緒。特別地,即使對 WeakHashMap 例項進行同步,並且沒有呼叫任何賦值方法,在一段時間後 size 方法也可能返回較小的值,對於 isEmpty 方法,返回 false,然後返回true,對於給定的鍵,containsKey 方法返回 true 然後返回 false,對於給定的鍵,get 方法返回一個值,但接著返回 null,對於以前出現在對映中的鍵,put 方法返回 null,而 remove 方法返回 false,對於鍵 set、值 collection 和條目 set 進行的檢查,生成的元素數量越來越少。
WeakHashMap 中的每個鍵物件間接地儲存為一個弱引用的指示物件。因此,不管是在對映內還是在對映之外,只有在垃圾回收器清除某個鍵的弱引用之後,該鍵才會自動移除。
(二) 引用物件的四種分類:
Api圖片如下:
1. 強引用(Strong Reference)
弱引用(Weak Reference)
軟引用(Soft Reference)
幻象引用(Phantom Reference)
(三) 深入理解
至此,對於WeakHashMap有了一個基本概念,但是還是比較模糊。找到一篇英文文件,將要點總結如下(如有翻譯不對,請指出,謝謝):
- 強引用
強引用就是普通的Java引用,程式碼:
StringBuffer buffer = new StringBuffer();
這句程式碼建立了一個StringBuffer物件,在變數buffer中儲存了這個物件的一個強引用。強引用之所以稱之為“強”(Strong),是因為他們與垃圾回收器(garbage collector)互動的方式。特別是(specifically),如果一個物件通過強引用連線(strongly reachable-強引用可到達),那麼它就不在垃圾回收期處理之列。當你正在使用某個物件而不希望垃圾回收期銷燬這個物件時,強引用通常正好能滿足你所要的。
- 當強引用太強的時候
假定你要使用一個final類Widget,但是基於某種原因,你不能繼承(extend)這個類或者通過其他方法為這個類增加一些新的功能。如果你需要跟蹤(track)這個類的不同物件的序列號。那麼可以將這些物件放入HashMap中,獲得不同的value值,這樣就可以做到通過不同的value值跟蹤這些物件了。程式碼:
serialNumberMap.put(widget, widgetSerialNumber);
但是widget的強引用會產生一些問題。在我們不需要一個Widget的序列號時,我們需要將這個Widget對應的Entry從HashMap中移除。否則我們可能面臨記憶體洩露(memory leak)的問題(如果我們沒有在應當移除Widgt的時候移除它),或者我們將莫名其妙的丟失序列號(如果我們正在使用Widget時卻移除了它)。如果這些問題類似,那麼這些問題是無垃圾回收機制(non-garbage-collected language)的程式語言的開發者所面臨的問題。Java的開發者不需要擔心這種問題。
W:如果一個物件具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空 間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。
強引用的另外一個常見問題就是圖片快取(cache)。普通的強引用將使得Image繼續儲存在記憶體中。在一些情況下,我們不需要有些Image繼續留在記憶體中,我們需要將這些圖片從記憶體中移出,這時,我們將扮演垃圾回收期的角色來決定哪些照片被移除。使這些被移出的圖片被垃圾回收器銷燬。下一次,你被迫再次扮演垃圾回收期的角色,手動決定哪些Image被回收。
Note:我覺得也可以用物件的hashCode來跟蹤物件。作者在此所舉的例子,只在說明Strong Reference。
- 弱引用
簡單說,就是弱引用不足以將其連線的物件強制儲存在記憶體中。弱引用能夠影響(leverage)垃圾回收器的某個物件的可到達級別。程式碼:
WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);
你可以使用weakWidget.get()方法老獲取實際的強引用物件。但是在之後,有可能突然返回null值(如果沒有其他的強引用在Widget之上),因為這個弱引用被回收。其中包裝的Widget也被回收。
解決Widget序列號的問題,最簡單的方法就是使用WeakHashmap。其key值為弱引用。如果一個WeakHashmap的key變成垃圾,那麼它對應用的value也自動的被移除。
W:垃圾回收期並不會總在第一次就找到弱引用,而是會找幾次才能找到。
- 引用佇列(Reference Quene)
一旦弱引用返回null值,那麼其指向的物件(即Widget)就變成了垃圾,這個弱引用物件(即weakWidget)也就沒有用了。這通常意味著要進行一定方式的清理(cleanup)。例如,WeakHashmap將會移除一些死的(dread)的entry,避免持有過多死的弱引用。
ReferenceQuene能夠輕易的追蹤這些死掉的弱引用。可以講ReferenceQuene傳入WeakHashmap的構造方法(constructor)中,這樣,一旦這個弱引用指向的物件成為垃圾,這個弱引用將加入ReferenceQuene中。
如下圖所示:
- 軟引用
除了在丟擲自己所指向的物件的迫切程度方面不一樣之外,軟引用和弱引用基本一樣。一個物件為弱可到達(或者指向這個物件的強引用是一個弱引用物件-即強引用的弱引用封裝),這個物件將在一個垃圾回收迴圈內被丟棄。但是,弱引用物件會保留一段時間之後才會被丟棄。
軟引用的執行和弱引用並沒有任何不同。但是,在供應充足(in plentiful supply)的情況下,軟可到達物件將在記憶體中儲存儘可能長的時間。這使得他們在記憶體中有絕佳的存在基礎(即有儘可能長存在的基礎)。因為你讓垃圾回收器去擔心兩件事情,一件是這個物件的可到達性,一件是垃圾回收期多麼想要這些物件正在消耗的記憶體。
- 幻象引用(phantom reference)
幻象引用於弱引用和軟引用均不同。它控制其指向的物件非常弱(tenuous),以至於它不能獲得這個物件。get()方法通常情況下返回的是null值。它唯一的作用就是跟蹤列隊在ReferenceQuene中的已經死去的物件。
幻象引用和弱引用的不同在於其入隊(enquene)進入ReferenceQuene的方式。當弱引用的物件成為若可到達時,弱引用即列隊進入ReferenceQuene。這個入隊發生在終結(finialize)和垃圾回收實際發生之前。理論上,通過不正規(unorthodox)的finilize()方法,成為垃圾的物件能重新復活(resurrected),但是弱引用仍然是死的。幻象引用只有當物件在物理上從記憶體中移出時,才會入隊。這就阻止我們重新恢復將死的物件。
W:終結(Finalization)指比拉圾回收更一般的概念,可以回收物件所佔有的任意資源,比如檔案描述符和圖形上下文等。
幻象引用由兩個好處:
A:它能確定某一個物件從記憶體中移除的時間,這也是唯一的方式。通常情況下,這不是非常有用。但是遲早(come in handy)會用到手動處理大圖片的情況:如果你確定一張圖片需要被垃圾回收,那麼在你嘗試載入下一張照片前,你應該等待這張照片被回收完成。這樣就使得令人恐懼的(dreaded)記憶體溢位不太可能發生。
B:虛幻引用能夠避終結(finilize)的基本問題。finilize()方法能夠通過給一個垃圾物件關聯一個強引用使之復活。問題是覆寫了finilize()方法的物件在成為垃圾之前,為了回收,垃圾回收期需要執行兩次單獨的迴圈。第一輪迴圈確定某個物件是垃圾,那麼它就符合終結finilize的條件。在finilize的過程中,這個物件可能被“復活”。在這個物件被實際移除之前,垃圾回收期不得不重新執行一遍。因為finilization並不是實時呼叫的,所以在終止進行的過程中,可能發生了gc的多次迴圈。在實際清理垃圾物件時,這導致了一些延時滯後。這將導致Heap中有大量的垃圾導致記憶體溢位。
幻象引用不可能發生以上的情況,當幻象引用入隊時,它實際上已經被移除了記憶體。幻象記憶體無法“復活”物件。這發現這個物件時虛幻可到達時,在第一輪迴圈中,它就被回收。
可以證明,finilize()方法從不在第一種情況下使用,但是虛幻引用提供了一種更安全和有效的使用和被排除掉的finilize方法的機制,使得垃圾回收更加簡單。但是因為有太多的東西需要實現,我通常不適用finilize。
W:Object類中相關內容如下:
文件中相關內容如下:
(四) 原文地址及其他翻譯