如何做到WeakHashMap?
在Java或者是Android程式設計中,我們一般都會使用到Map,比如HashMap這樣的具體實現。更高階一點,我們可能會使用WeakHashMap。
WeakHashMap其實和HashMap大多數行為是一樣的,只是WeakHashMap不會阻止GC回收key物件(不是value),那麼WeakHashMap是怎麼做到的呢,這就是我們研究的主要問題。
在開始WeakHashMap之前,我們先要對弱引用有一定的瞭解。
在Java中,有四種引用型別
- 強引用(Strong Reference),我們正常編碼時預設的引用型別,強應用之所以為強,是因為如果一個物件到GC Roots強引用可到達,就可以阻止GC回收該物件
- 軟引用(Soft Reference)阻止GC回收的能力相對弱一些,如果是軟引用可以到達,那麼這個物件會停留在記憶體更時間上長一些。當記憶體不足時垃圾回收器才會回收這些軟引用可到達的物件
- 弱引用(WeakReference)無法阻止GC回收,如果一個物件時弱引用可到達,那麼在下一個GC回收執行時,該物件就會被回收掉。
-
虛引用(Phantom Reference)十分脆弱,它的唯一作用就是當其指向的物件被回收之後,自己被加入到引用佇列,用作記錄該引用指向的物件已被銷燬
這其中還有一個概念叫做引用佇列(Reference Queue)
一般情況下,一個物件標記為垃圾(並不代表回收了)後,會加入到引用佇列。
對於虛引用來說,它指向的物件會只有被回收後才會加入引用佇列,所以可以用作記錄該引用指向的物件是否回收。
WeakHashMap如何不阻止物件回收呢
private static final class Entry<K, V> extends WeakReference<K> implements Map.Entry<K, V> { int hash; boolean isNull; V value; Entry<K, V> next; interface Type<R, K, V> { R get(Map.Entry<K, V> entry); } Entry(K key, V object, ReferenceQueue<K> queue) { super(key, queue); isNull = key == null; hash = isNull ? 0 : key.hashCode(); value = object; }
如原始碼所示,
- WeakHashMap的Entry繼承了WeakReference。
- 其中Key作為了WeakReference指向的物件
- 因此WeakHashMap利用了WeakReference的機制來實現不阻止GC回收Key
如何刪除被回收的key資料呢
在Javadoc中關於WeakHashMap有這樣的描述,當key不再引用時,其對應的key/value也會被移除。
那麼是如何移除的呢,這裡我們通常有兩種假設策略
- 當物件被回收的時候,進行通知
-
WeakHashMap輪詢處理時效的Entry
而WeakHashMap採用的是輪詢的形式,在其put/get/size等方法呼叫的時候都會預先呼叫一個poll的方法,來檢查並刪除失效的Entry
void poll() { Entry<K, V> toRemove; while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) { removeEntry(toRemove); Log.d(LOGTAG, "removeEntry=" + toRemove.value); } }
為什麼沒有使用看似更好的通知呢,我想是因為在Java中沒有一個可靠的通知回撥,比如大家常說的finalize方法,其實也不是標準的,不同的JVM可以實現不同,甚至是不呼叫這個方法。
當然除了單純的看原始碼,進行合理的驗證是檢驗分析正確的一個重要方法。
這裡首先,我們定義一個MyObject類,處理一下finalize方法(在我的測試機上可以正常呼叫,僅僅做為輔助驗證手段)
class MyObject(val id: String) : Any() { protected fun finalize() { Log.i("MainActivity", "Object($id) finalize method is called") } }
然後是呼叫者的程式碼,如下
private val weakHashMap = WeakHashMap<Any, Int>() var count : Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) dumpWeakInfo() fab.setOnClickListener { view -> //System.gc()// this seldom works use Android studio force gc stop weakHashMap.put(MyObject(count.toString()), count) count ++ dumpWeakInfo() Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } } fun dumpWeakInfo() { Log.i("MainActivity", "dumpWeakInfo weakInfo.size=${weakHashMap.size}") }
我們按照如下操作
- 點選fab控制元件,每次對WeakhashMap物件增加一個Entry,並列印WeakHashMap的size 執行3此
- 在沒有強制觸發GC時,WeakHashMap物件size一直會增加
- 手動出發Force GC,我們會看到MyObject有finalize方法被呼叫
- 再次點選fab空間,然後輸出的WeakHashMap size急劇減少。
- 同樣我們收到在WeakHashMap增加的日誌也會輸出
I/MainActivity(10202): dumpWeakInfo weakInfo.size=1 I/MainActivity(10202): dumpWeakInfo weakInfo.size=2 I/MainActivity(10202): dumpWeakInfo weakInfo.size=3 I/MainActivity(10202): Object(2) finalize method is called I/MainActivity(10202): Object(1) finalize method is called I/MainActivity(10202): Object(0) finalize method is called I/WeakHashMap(10202): removeEntry=2 I/WeakHashMap(10202): removeEntry=0 I/WeakHashMap(10202): removeEntry=1 I/MainActivity(10202): dumpWeakInfo weakInfo.size=1
最後注意:System.gc()並不一定可以工作,建議使用Android Studio的Force GC