在《Effective Java》中的p23頁有涉及到WeakHashMap
的相關知識,在這篇文章中做一個總結以及介紹一下相關知識。
在這裏我們分成三個部分來說明一下,這只是我自己參看JDK源碼和上網搜索資料得到的結果,如有錯誤,歡迎指出,我不勝榮幸。
WeakHashMap和HashMap有什麼不同
我們知道WeakHashMap
是弱引用,而HashMap
是強引用。
這就是說當我們給Java虛擬機分配的內存不足的時候,HashMap
寧可拋出OutOfMemoryError
異常也不會回收其相應的沒有被引用的對象,而我們的WeakHashMap
則會回收存儲在其中但有被引用的對象。
如下的程序實例,運用的JVM參數爲:-Xmx5m -Xms5m -XX:+PrintGC
HashMap
的測試代碼
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for (int i = 0; ; i++) {
hashMap.put(i, new String("HashMap"));//一直往裏面加數據
if (i % 1000 == 0) { //每隔一千次判斷一下有沒有對象被回收
for (int j = 0; j < i; j++) {//遍歷一遍
if (hashMap.get(j) == null) {
System.out.println("第" + j + "個對象開始回收");
return;
}
}
}
}
}
我們運行程序,發現沒有任何對象被回收,最後拋出了異常
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.addEntry(HashMap.java:753)
at java.util.HashMap.put(HashMap.java:385)
那我們再來證明一下WeakHashMap
會回收其中存儲的沒有被引用的對象
public static void main(String[] args) {
WeakHashMap hashMap = new WeakHashMap();
for (int i = 0; ; i++) {
hashMap.put(i, new String("WeakHashMap"));//一直往裏面加數據
if (i % 1000 == 0) { //每隔一千次判斷一下有沒有對象被回收
for (int j = 0; j < i; j++) {//遍歷一遍
if (hashMap.get(j) == null) {
System.out.println("第" + j + "個對象開始回收");
return;
}
}
}
}
}
最後程序的輸出如下
[GC 1408K->787K(4992K), 0.0067048 secs]
第241個對象開始回收
可知WeakHashMap
將其中存儲的鍵爲241的對象開始回收了。
我們接下來思考,WeakHashMap
是怎樣知道要回收對象的呢?
WeakHashMap
通過將一些沒有被引用的鍵的值賦值爲null
,這樣的話就會告知GC去回收這些存儲的值了。
那麼也就是說,如果我們將其所有的鍵都添加引用,那麼其就不會被回收了?我們來寫一段代碼測試一下
public static void main(String[] args) {
WeakHashMap weakHashMap = new WeakHashMap();
HashMap hashMap=new HashMap();
for (int i = 0; ; i++) {
Integer num=new Integer(i);
hashMap.put(i,num); // num 被引用
weakHashMap.put(num, new String("WeakHashMap"));//將num改爲i就會有對象被回收
if (i % 1000 == 0) { //每隔一千次判斷一下有沒有對象被回收
for (int j = 0; j < i; j++) {//遍歷一遍
if (weakHashMap.get(j) == null) {
System.out.println("第" + j + "個對象開始回收");
return;
}
}
}
}
}
我們在HashMap
中存儲了WeakHashMap
的鍵,這樣就會是的其不會被回收,最後測試結果表示沒有對象被回收,程序也就像我們期待的一下報了OutOfMemoryError
。
當然可能會有人說,爲什麼這個OutOfMemoryError
不是我們的HashMap
引起的呢,然後WeakHashMap
還沒有達到回收的條件?
針對這一點,我根據程序中的註釋將WeakHahMap
的鍵從num改爲i,最後會發現有對象被回收。
既然WeakHashMap
是將我們的鍵值設置爲null
從而引起GC的,那麼我們將null
作爲鍵存進去,爲什麼不會導致被回收呢?
這時候我們就需要看看其JDK的有關put
方法的源碼了
public V put(K key, V value) {
K k = (K) maskNull(key);// 重點看這裏
int h = HashMap.hash(k.hashCode());
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<K,V>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}
我們重點看一下開始的判斷鍵時候爲null
的maskNull()
方法。
private static Object maskNull(Object key) {
return (key == null ? NULL_KEY : key);
}
我們發現,如果key
是null
的話,返回的是NULL_KEY
這個靜態值,我們再來看一下這個值是什麼的時候,就恍然大悟了
/**
* Value representing null keys inside tables.
*/
private static final Object NULL_KEY = new Object();
WeakHashMap
在存儲null
爲鍵的時候,其實存儲的是其本身的靜態成員變量Object
,也就是說存儲的不是null
。
這也就解釋了爲什麼存儲鍵爲null
不會被馬上回收。
最後我們來看一下程序是在什麼時候來判斷要將沒有引用的key
標記爲null
的呢?
這時候WeakHashMap
中的一個非常重要的方法expungeStaleEntries()
就登場了。
在WeakHashMap
的put()
,get()
,remove()
等等方法中都調用了一個getTable()
方法,而這個getTable()
方法的源碼如下:
private Entry[] getTable() {
expungeStaleEntries();
return table;
}
可以知道他們其實調用的都是expungeStaleEntries()
方法。
可知這個方法是一個非常重要的方法。
[注].出自: Java集合框架:WeakHashMap,一篇非常優秀的博客!
首先我們看一下其實現的源碼
/**
* Expunges stale entries from the table.
*/
private void expungeStaleEntries() {
Entry<K,V> e;
while ( (e = (Entry<K,V>) queue.poll()) != null) {
int h = e.hash;
int i = indexFor(h, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
e.next = null; // Help GC
e.value = null; // " "
size--;
break;
}
prev = p;
p = next;
}
}
}
可以看到每調用一次expungeStaleEntries()
方法,就會在引用隊列中尋找是否有將要被清除的key
對象,如果有則在table中找到其值,並將value
設置爲null
,next
指針也設置爲null
,讓GC去回收這些資源。
2017-11-18 15:23 於上海