WeakHashMap
WeakHashMap介紹
java.lang.Object
? java.util.AbstractMap<K, V>
? java.util.WeakHashMap<K, V>
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {}
WeakHashMap繼承于AbstractMap,實(shí)現(xiàn)了Map接口聚凹。和HashMap一樣驰弄,WeakHashMap也是一個(gè)散列表乾闰,它存儲(chǔ)的內(nèi)容也是鍵值對(key-value)映射,而且鍵和值都可以是null疏日。
不過WeakHashMap的鍵是“弱鍵”偿洁。在WeakHashMap中,當(dāng)某個(gè)鍵不再正常使用時(shí)沟优,會(huì)從WeakHashMap中被自動(dòng)移除涕滋。更精確地說,對于一個(gè)給定的鍵挠阁,其映射的存在并不阻止垃圾回收器對該鍵的回收宾肺,這就使該鍵成為可終止的溯饵,被終止,然后被回收锨用。某個(gè)鍵被終止時(shí)丰刊,它對應(yīng)的鍵值對也就從映射中有效地移除了。
和HashMap一樣增拥,WeakHashMap是不同步的啄巧。可以使用 Collections.synchronizedMap方法來構(gòu)造同步的WeakHashMap掌栅。
WeakHashMap的構(gòu)造函數(shù)
-
默認(rèn)構(gòu)造函數(shù)秩仆,使用默認(rèn)初始化容量:16,默認(rèn)負(fù)載因子:0.75f
WeakHashMap()
-
指定“容量大小”的構(gòu)造函數(shù)猾封,使用默認(rèn)負(fù)載因子:0.75f
WeakHashMap(int capacity)
-
指定“容量大小”和“負(fù)載因子”的構(gòu)造函數(shù)
WeakHashMap(int capacity, float loadFactor)
-
包含“子Map”的構(gòu)造函數(shù)
WeakHashMap(Map<? extends K, ? extends V> map)
WeakHashMap的屬性
- Entry<K,V>[] table
table是一個(gè)Entry數(shù)組澄耍,而Entry實(shí)際上就是一個(gè)單向鏈表。哈希表的“key-value鍵值對”都是存儲(chǔ)在Entry數(shù)組中的晌缘。
- private int size
size是Hashtable的大小齐莲,它是Hashtable保存的鍵值對的數(shù)量。
- private int threshold
threshold是閾值枚钓,用于判斷是否需要調(diào)整容量铅搓。threshold=“容量*負(fù)載因子”。
- private final float loadFactor
loadFactor就是負(fù)載因子搀捷,它是衡量哈希表能放多滿的數(shù)值。
- int modCount
modCount是用來實(shí)現(xiàn)fail-fast機(jī)制的多望,記錄哈希表被修改的次數(shù)嫩舟。
- private final ReferenceQueue<Object> queue = new ReferenceQueue<>()
引用隊(duì)列:當(dāng)弱引用所引用的對象被垃圾回收器回收時(shí),該弱引用將被垃圾回收器添加到與之關(guān)聯(lián)的引用隊(duì)列中怀偷。
WeakHashMap的關(guān)鍵實(shí)現(xiàn)
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
Entry繼承自WeakReference(弱引用)家厌,那么Entry本身就是一個(gè)弱引用。
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
從Entry的構(gòu)造函數(shù)中可以看出:Entry通過傳入key和queue調(diào)用了父類WeakReference的構(gòu)造函數(shù)椎工,那么key就成為了這個(gè)弱引用所引用的對象饭于,并把這個(gè)弱引用注冊到了引用隊(duì)列上。
如果一個(gè)對象只具有弱引用维蒙,那就類似于可有可無的生活用品掰吕。只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中颅痊,一旦發(fā)現(xiàn)了只具有弱引用的對象殖熟,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存斑响。不過菱属,由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程钳榨, 因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對象。 弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用纽门,如果弱引用所引用的對象被垃圾回收薛耻,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
因?yàn)榇鎯?chǔ)在Entry中的key只具有弱引用赏陵,所以并不能阻止垃圾回收線程對它進(jìn)行回收昭卓,當(dāng)發(fā)生垃圾回收時(shí),Entry中的key被回收瘟滨,java虛擬機(jī)就會(huì)把這個(gè)Entry添加到與之關(guān)聯(lián)的queue中去候醒。
通過上面的分析,存儲(chǔ)在WeakHashMap中的key隨時(shí)都會(huì)面臨被回收的風(fēng)險(xiǎn)杂瘸,因此每次查詢WeakHashMap時(shí)倒淫,都要確認(rèn)當(dāng)前WeakHashMap是否已經(jīng)有key被回收了。當(dāng)key被回收時(shí)败玉,引用這個(gè)key的Entry對象就會(huì)被添加到引用隊(duì)列中去敌土,所以只要查詢引用隊(duì)列是否有Entry對象,就可以確認(rèn)是否有key被回收了运翼。WeakHashMap通過調(diào)用expungeStaleEntries
方法來清除已經(jīng)被回收的key所關(guān)聯(lián)的Entry對象返干。
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, 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;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
WeakHashMap在調(diào)用put
和get
方法之前,都會(huì)調(diào)用expungeStaleEntries
方法來清除已經(jīng)被回收的key所關(guān)聯(lián)的Entry對象血淌。因?yàn)镋ntry是弱引用矩欠,即使引用著key對象,但是依然不能阻止垃圾回收線程對key對象的回收悠夯。
如果存放在WeakHashMap中的key都存在強(qiáng)引用癌淮,那么WeakHashMap就會(huì)退化成HashMap。如果在系統(tǒng)中希望通過WeakHashMap自動(dòng)清除數(shù)據(jù)沦补,請盡量不要在系統(tǒng)的其他地方強(qiáng)引用WeakHashMap的key乳蓄,否則,這些key就不會(huì)被回收夕膀,WeakHashMap也就無法正常釋放它們所占用的表項(xiàng)虚倒。
案例應(yīng)用
如果在一個(gè)普通的HashMap中存儲(chǔ)一些比較大的值如下:
Map<Integer,Object> map = new HashMap<>();
for(int i=0;i<10000;i++)
{
Integer ii = new Integer(i);
map.put(ii, new byte[i]);
}
運(yùn)行參數(shù):-Xmx5M
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at collections.WeakHashMapTest.main(WeakHashMapTest.java:39)
如果我們將HashMap換成WeakHashMap其余都不變:
Map<Integer,Object> map = new WeakHashMap<>();
for(int i=0;i<10000;i++)
{
Integer ii = new Integer(i);
map.put(ii, new byte[i]);
}
運(yùn)行結(jié)果:(無任何報(bào)錯(cuò))
這兩段代碼比較可以看到WeakHashMap的功效,如果在系統(tǒng)中需要一張很大的Map表产舞,Map中的表項(xiàng)作為緩存使用魂奥,這也意味著即使沒能從該Map中取得相應(yīng)的數(shù)據(jù),系統(tǒng)也可以通過候選方案獲取這些數(shù)據(jù)庞瘸。雖然這樣會(huì)消耗更多的時(shí)間捧弃,但是不影響系統(tǒng)的正常運(yùn)行。
在這種場景下,使用WeakHashMap是最合適的违霞。因?yàn)閃eakHashMap會(huì)在系統(tǒng)內(nèi)存范圍內(nèi)嘴办,保存所有表項(xiàng),而一旦內(nèi)存不夠买鸽,在GC時(shí)涧郊,沒有被引用的表項(xiàng)又會(huì)很快被清除掉,從而避免系統(tǒng)內(nèi)存溢出眼五。
我們這里稍微改變一下上面的代碼(加了一個(gè)List):
Map<Integer,Object> map = new WeakHashMap<>();
List<Integer> list = new ArrayList<>();
for(int i=0;i<10000;i++)
{
Integer ii = new Integer(i);
list.add(ii);
map.put(ii, new byte[i]);
}
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at collections.WeakHashMapTest.main(WeakHashMapTest.java:43)
如果存放在WeakHashMap中的key都存在強(qiáng)引用妆艘,那么WeakHashMap就會(huì)退化成HashMap。如果在系統(tǒng)中希望通過WeakHashMap自動(dòng)清除數(shù)據(jù)看幼,請盡量不要在系統(tǒng)的其他地方強(qiáng)引用WeakHashMap的key批旺,否則,這些key就不會(huì)被回收诵姜,WeakHashMap也就無法正常釋放它們所占用的表項(xiàng)汽煮。
要想WeakHashMap能夠釋放掉被回收的key關(guān)聯(lián)的value對象,要盡可能的多調(diào)用下put/size/get等操作棚唆,因?yàn)檫@些方法會(huì)調(diào)用expungeStaleEntries方法暇赤,expungeStaleEntries方法是關(guān)鍵,而如果不操作WeakHashMap宵凌,以企圖WeakHashMap“自動(dòng)”釋放內(nèi)存是不可取的鞋囊,這里的“自動(dòng)”是指譬如:map.put(obj, new byte[10M]);之后obj=null了瞎惫,之后再也沒調(diào)用過map的任何方法溜腐,那么new出來的10M空間是不會(huì)釋放的。
注意
WeakHashMap的key可以為null微饥,那么當(dāng)put一個(gè)key為null逗扒,value為一個(gè)很大對象的時(shí)候,這個(gè)很大的對象怎么采用WeakHashMap的自帶功能自動(dòng)釋放呢欠橘?
代碼如下:
Map<Object,Object> map = new WeakHashMap<>();
map.put(null,new byte[5*1024*928]);
int i = 1;
while(true)
{
System.out.println();
TimeUnit.SECONDS.sleep(2);
System.out.println(map.size());
System.gc();
System.out.println("==================第"+i+++"次GC結(jié)束====================");
}
運(yùn)行參數(shù):-Xmx5M -XX:+PrintGCDetails
運(yùn)行結(jié)果:
1
[GC [PSYoungGen: 680K->504K(2560K)] 5320K->5240K(7680K), 0.0035741 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 504K->403K(2560K)] [ParOldGen: 4736K->4719K(5120K)] 5240K->5123K(7680K) [PSPermGen: 2518K->2517K(21504K)], 0.0254473 secs] [Times: user=0.06 sys=0.00, real=0.03 secs]
==================第1次GC結(jié)束====================
1
[Full GC [PSYoungGen: 526K->0K(2560K)] [ParOldGen: 4719K->5112K(5120K)] 5246K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0172785 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
==================第2次GC結(jié)束====================
1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0178421 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
==================第3次GC結(jié)束====================
1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0164874 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
==================第4次GC結(jié)束====================
1
[Full GC [PSYoungGen: 41K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5153K->5112K(7680K) [PSPermGen: 2520K->2520K(21504K)], 0.0191096 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
==================第5次GC結(jié)束====================
(一直循環(huán)下去)
可以看到在map.put(null, new byte[5*1024*928]);
之后,相應(yīng)的內(nèi)存一直沒有得到釋放现恼。
通過顯式的調(diào)用map.remove(null)
可以將內(nèi)存釋放掉肃续,如下代碼所示:
Map<Integer,Object> map = new WeakHashMap<>();
System.gc();
System.out.println("===========gc:1=============");
map.put(null,new byte[4*1024*1024]);
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:2=============");
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:3=============");
map.remove(null);
TimeUnit.SECONDS.sleep(5);
System.gc();
System.out.println("===========gc:4=============");
運(yùn)行參數(shù):-Xmx5M -XX:+PrintGCDetails
運(yùn)行結(jié)果:
[GC [PSYoungGen: 720K->504K(2560K)] 720K->544K(6144K), 0.0023652 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 40K->480K(3584K)] 544K->480K(6144K) [PSPermGen: 2486K->2485K(21504K)], 0.0198023 secs] [Times: user=0.11 sys=0.00, real=0.02 secs]
===========gc:1=============
[GC [PSYoungGen: 123K->32K(2560K)] 4699K->4608K(7680K), 0.0026722 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4576K->4578K(5120K)] 4608K->4578K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0145734 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
===========gc:2=============
[GC [PSYoungGen: 40K->32K(2560K)] 4619K->4610K(7680K), 0.0013068 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4578K->4568K(5120K)] 4610K->4568K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0189642 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
===========gc:3=============
[GC [PSYoungGen: 40K->32K(2560K)] 4609K->4600K(7680K), 0.0011742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 4568K->472K(5120K)] 4600K->472K(7680K) [PSPermGen: 2519K->2519K(21504K)], 0.0175907 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
===========gc:4=============
Heap
PSYoungGen total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14820,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 5120K, used 472K [0x00000000ff800000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 5120K, 9% used [0x00000000ff800000,0x00000000ff876128,0x00000000ffd00000)
PSPermGen total 21504K, used 2526K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000)
object space 21504K, 11% used [0x00000000fa600000,0x00000000fa8778f8,0x00000000fbb00000)
分析:
1、在WeakHashMap中叉袍,put的key為null時(shí)始锚,放入的是NULL_KEY
,即:private static final Object NULL_KEY = new Object()喳逛,是一個(gè)靜態(tài)常量瞧捌。
2、在WeakHashMap中,由于傳給WeakReference的只有key和queue姐呐,即gc只回收里面的KEY殿怜,而不會(huì)動(dòng)value,value的清除則是在expungeStaleEntries這個(gè)私有方法進(jìn)行的曙砂。
3头谜、而static的就不在gc之列,所以key也就不會(huì)被gc鸠澈,所以它的大值value柱告,也就不會(huì)被設(shè)為null,不會(huì)被回收笑陈。
4际度、通過調(diào)用remove方法,最終table[k]設(shè)為null涵妥,此時(shí)大對象游離所以被回收乖菱。
只有通過remove方法才能刪除null鍵所關(guān)聯(lián)的value,建議在使用WeakHashMap的時(shí)候盡量避免使用null作為鍵妹笆。