WeakHashMap的垃圾回收原理詳解
WeakHashMap 介紹
WeakHashMap 與 HashMap 的用法基本類(lèi)似。與 HashMap 的區(qū)別在于,HashMap 的key 保留了對(duì)實(shí)際對(duì)象的強(qiáng)引用個(gè),這意味著只要該HashMap對(duì)象不被銷(xiāo)毀,該HashMap的所有key所引用的對(duì)象就不會(huì)被垃圾回收,HashMap也不會(huì)自動(dòng)刪除這些key所對(duì)應(yīng)的key-value 對(duì);
但WeakHashMap的key 只保留了對(duì)實(shí)際對(duì)象的弱引用,這意味著如果WeakHashMap對(duì)象的key所引用的對(duì)象沒(méi)有被其他強(qiáng)引用變量所引用個(gè),則這些key所引用的對(duì)象可能被垃圾回收,WeakHashMap也可能自動(dòng)刪除這些key所對(duì)應(yīng)的key-value對(duì)。
WeakHashMap的數(shù)據(jù)結(jié)構(gòu)
類(lèi)的定義
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> { }
WeakHashMap 因?yàn)镚C的時(shí)候會(huì)把沒(méi)有強(qiáng)引用的key回收掉,所以它里面的元素不會(huì)太多。
因此,WeakHashMap 的存儲(chǔ)結(jié)構(gòu)只有 數(shù)組 + 鏈表
變量和常量
// 默認(rèn)初始容量為16 private static final int DEFAULT_INITIAL_CAPACITY = 16; // 最大容量為2的30次方 private static final int MAXIMUM_CAPACITY = 1 << 30; //默認(rèn)裝載因子 private static final float DEFAULT_LOAD_FACTOR = 0.75f; // 桶 Entry<K,V>[] table; //元素個(gè)數(shù) private int size; // 擴(kuò)容門(mén)檻,等于capacity * loadFactor private int threshold; // 裝載因子 private final float loadFactor; /** * 引用隊(duì)列,當(dāng)弱鍵失效的時(shí)候會(huì)把Entry添加到這個(gè)隊(duì)列中 */ private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 修改次數(shù) int modCount;
Entry 內(nèi)部類(lèi)
Entry 內(nèi)部類(lèi)并沒(méi)有key屬性,因?yàn)閗ey屬性存儲(chǔ)在 Reference 類(lèi)中
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; final int hash; Entry<K,V> next; /** * Creates new entry. */ Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { // 調(diào)用Reference的構(gòu)造方法初始化key和引用隊(duì)列 super(key, queue); this.value = value; this.hash = hash; this.next = next; } } public class WeakReference<T> extends Reference<T> { // 調(diào)用Reference的構(gòu)造方法初始化 key 和 引用隊(duì)列 public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } } public abstract class Reference<T> { // 實(shí)際存儲(chǔ)key的地方 private T referent; // 引用隊(duì)列 volatile ReferenceQueue<? super T> queue; Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; } }
從Entry的構(gòu)造方法我們知道,key和queue最終會(huì)傳到到Reference的構(gòu)造方法中,這里的key就是Reference的referent屬性,它會(huì)被gc特殊對(duì)待,即當(dāng)沒(méi)有強(qiáng)引用存在時(shí),當(dāng)下一次gc的時(shí)候會(huì)被清除。
構(gòu)造方法
public WeakHashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor); int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; table = newTable(capacity); this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); } public WeakHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public WeakHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public WeakHashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAll(m); }
構(gòu)造方法和 HashMap基本類(lèi)似,初始容量為大于等于傳入容量的2的n次方,擴(kuò)容的門(mén)檻 threshold 等于 capacity * loadFactor 。
put(K key, V value)
public V put(K key, V value) { // 如果key為空,用空對(duì)象代替 Object k = maskNull(key); // 計(jì)算key的hash值 int h = hash(k); // 獲取桶 Entry<K,V>[] tab = getTable(); // 計(jì)算元素在哪個(gè)桶中,h & (length-1) int i = indexFor(h, tab.length); // 遍歷桶對(duì)應(yīng)的鏈表 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++; // 如果沒(méi)找到就把新值插入到鏈表的頭部 Entry<K,V> e = tab[i]; tab[i] = new Entry<>(k, value, queue, h, e); // 如果插入元素后數(shù)量達(dá)到了擴(kuò)容門(mén)檻就把桶的數(shù)量擴(kuò)容為2倍大小 if (++size >= threshold) resize(tab.length * 2); return null; }
(1)計(jì)算hash;
與HashMap不同的是 ,key為null 時(shí),返回的hash時(shí)0
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
而 WeakHashMap 用空對(duì)象來(lái)計(jì)算
private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; }
另外,HashMap 計(jì)算hash 只用了依次異或,而這里使用了四次
final int hash(Object k) { int h = k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
(2) 計(jì)算再哪個(gè)桶
(3) 遍歷桶對(duì)應(yīng)的鏈表
(4) 如果能找到元素,則用新值代替舊值
(5) 如果沒(méi)有找到就在鏈表的頭部插入新元素
(6) 如果元素?cái)?shù)量達(dá)到了擴(kuò)容門(mén)檻,就把容量擴(kuò)大到原來(lái)容量的2倍;
resize(int newCapacity)
void resize(int newCapacity) { // 獲取舊桶,getTable()的時(shí)候會(huì)剔除失效的Entry Entry<K,V>[] oldTable = getTable(); // 舊容量 int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新桶 Entry<K,V>[] newTable = newTable(newCapacity); // 把元素從舊桶轉(zhuǎn)移到新桶 transfer(oldTable, newTable); table = newTable; // 如果元素個(gè)數(shù)大于擴(kuò)容門(mén)檻的一半,則使用新桶和新容量,并計(jì)算新的擴(kuò)容門(mén)檻 if (size >= threshold / 2) { threshold = (int)(newCapacity * loadFactor); } else { // 否則把元素再轉(zhuǎn)移回舊桶,還是使用舊桶 // 因?yàn)樵趖ransfer的時(shí)候會(huì)清除失效的Entry,所以元素個(gè)數(shù)可能沒(méi)有那么大了,就不需要擴(kuò)容了 expungeStaleEntries(); transfer(newTable, oldTable); table = oldTable; } } private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) { // 遍歷舊桶 for (int j = 0; j < src.length; ++j) { Entry<K,V> e = src[j]; src[j] = null; while (e != null) { Entry<K,V> next = e.next; Object key = e.get(); // 如果key等于了null就清除,說(shuō)明key被gc清理掉了,則把整個(gè)Entry清除 if (key == null) { e.next = null; // Help GC e.value = null; // " " size--; } else { // 否則就計(jì)算在新桶中的位置并把這個(gè)元素放在新桶對(duì)應(yīng)鏈表的頭部 int i = indexFor(e.hash, dest.length); e.next = dest[i]; dest[i] = e; } e = next; } } }
(1)判斷舊容量是否達(dá)到最大容量;
(2)新建新桶并把元素全部轉(zhuǎn)移到新桶中;
(3)如果轉(zhuǎn)移后元素個(gè)數(shù)不到擴(kuò)容門(mén)檻的一半,則把元素再轉(zhuǎn)移回舊桶,繼續(xù)使用舊桶,說(shuō)明不需要擴(kuò) 容;
(4)否則使用新桶,并計(jì)算新的擴(kuò)容門(mén)檻;
(5)轉(zhuǎn)移元素的過(guò)程中會(huì)把key為null的元素清除掉,所以size會(huì)變??;
垃圾回收原理
WeakHashMap 通過(guò)將一些沒(méi)有被引用的鍵的值賦值為null, 這樣就會(huì)告訴GC去回收這些存儲(chǔ)的值。
我們看下面例子:
public class WeakHashMapTest { public static void main(String[] args) { House seller1 = new House("1號(hào)賣(mài)家房源."); SellerInfo sellerInfo1 = new SellerInfo(); House seller2 = new House("2號(hào)賣(mài)家房源"); SellerInfo sellerInfo2 = new SellerInfo(); WeakHashMap<House,SellerInfo> weakHashMap = new WeakHashMap<>(); //如果換成 HashMap ,則Key是對(duì)House對(duì)象的強(qiáng)引用 weakHashMap.put(seller1,sellerInfo1); weakHashMap.put(seller2,sellerInfo2); System.out.println("weakHashMap before null,size="+weakHashMap.size()); seller1 = null; System.gc(); System.runFinalization(); //如果換成 HashMap ,size 依然等于2 System.out.println("weakHashMap after null, size = "+weakHashMap.size()); System.out.println(weakHashMap); } } class SellerInfo{}
最終的結(jié)果是size = 1,為什么為1呢? 因?yàn)?seller1 為null, 從而引起GC,那么 為什么我們把 null 作為鍵存進(jìn)去,為什么不會(huì)導(dǎo)致被回收呢?
那么我們看 put
方法的源碼:
public V put(K key, V value) { K k = (K) maskNull(key);// 重點(diǎn)看這里 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; }
我們重點(diǎn)看一些 這行代碼 K k = (K) maskNull(key);
private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; }
如果key為null的話(huà),返回 的是NULL_KEY 這個(gè)靜態(tài)值,這個(gè)靜態(tài)值就是 Object ,所以WeakHashMap 在存儲(chǔ)null為鍵的時(shí)候,其實(shí)存儲(chǔ)的是其本身的靜態(tài)成員變量 Object,也就是存儲(chǔ)不是null。
那WeakHashMap 是如何跟WeakReference 關(guān)聯(lián)起來(lái)的呢?
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; final int hash; Entry<K,V> next; /** * Creates new entry. */ 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; } }
WeakHashMap的Entry是繼承WeakReference,這樣一來(lái),整個(gè)Entry就是一個(gè)WeakReference,再來(lái)看看Entry的構(gòu)造方法,調(diào)用了super(key, queue),也就是調(diào)用了這個(gè)構(gòu)造方法
public class WeakReference<T> extends Reference<T> { public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
有兩個(gè)參數(shù),一個(gè)key,一個(gè)是queue, 這個(gè)key就是WeakHashMap 中存儲(chǔ)的key的值,這個(gè)queue 是WeakHashMap 中創(chuàng)建的 ReferenceQueue 。 那么 ReferenceQueue 有什么用呢?
當(dāng)GC某個(gè)對(duì)象時(shí),如果有此對(duì)象上還有弱引用與其關(guān)聯(lián),會(huì)將WeakReference對(duì)象與Reference 類(lèi)的pending 引用關(guān)聯(lián)起來(lái),然后由 Reference Handler線(xiàn)程將該插入ReferenceQueue隊(duì)列。
也就是說(shuō)Entry中的key被GC時(shí),會(huì)你那個(gè)Entry 放入到 ReferenceQueue中,WeakHashMap就能通過(guò)ReferenceQueue中的Entry了解到哪些key已經(jīng)被GC,或者即將馬上被GC,起到了通知的作用。
那么什么時(shí)候來(lái)判斷要講沒(méi)有被引用的key標(biāo)記為null的呢?
在WeakHashMap的put(),get(),remove()等等方法中都調(diào)用了一個(gè)getTable()方法,而這個(gè)getTable()方法的源碼如下:
private Entry<K,V>[] getTable() { expungeStaleEntries(); return table; }
其實(shí)都是調(diào)用 expungeStaleEntries() 方法,我們看其源碼:
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; } } } }
上面代碼中的queue 就是定義的成員變量
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
可以看到每調(diào)用一次expungeStaleEntries()方法,就會(huì)在引用隊(duì)列中尋找是否有將要被清除的key對(duì)象,如果有則在table中找到其值,并將value設(shè)置為null,next指針也設(shè)置為null,讓GC去回收這些資源。
總結(jié)
(1)WeakHashMap使用(數(shù)組 + 鏈表)存儲(chǔ)結(jié)構(gòu);
(2)WeakHashMap中的key是弱引用,gc的時(shí)候會(huì)被清除;
(3)每次對(duì)map的操作都會(huì)剔除失效key對(duì)應(yīng)的Entry;
(4)使用String作為key時(shí),一定要使用new String()這樣的方式聲明key,才會(huì)失效,其它的基本類(lèi)型的包裝類(lèi)型是一樣的;
(5)WeakHashMap常用來(lái)作為緩存使用;
到此這篇關(guān)于WeakHashMap的垃圾回收原理詳解的文章就介紹到這了,更多相關(guān)WeakHashMap垃圾回收內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nacos客戶(hù)端配置中心緩存動(dòng)態(tài)更新實(shí)現(xiàn)源碼
這篇文章主要為大家介紹了Nacos客戶(hù)端配置中心緩存動(dòng)態(tài)更新實(shí)現(xiàn)源碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03Spring Cloud微服務(wù)架構(gòu)的構(gòu)建:分布式配置中心(加密解密功能)
這篇文章主要給大家介紹了關(guān)于Spring Cloud微服務(wù)架構(gòu)的構(gòu)建:分布式配置中心(加密解密)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2018-05-05利用spring boot如何快速啟動(dòng)一個(gè)web項(xiàng)目詳解
這篇文章主要給大家介紹了關(guān)于利用spring boot如何快速啟動(dòng)一個(gè)web項(xiàng)目的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧、2017-12-12SpringBoot項(xiàng)目集成Swagger和swagger-bootstrap-ui及常用注解解讀
這篇文章主要介紹了SpringBoot項(xiàng)目集成Swagger和swagger-bootstrap-ui及常用注解解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Spring 依賴(lài)注入實(shí)現(xiàn)示例
這篇文章主要介紹了Spring 依賴(lài)注入實(shí)現(xiàn)示例的相關(guān)資料,幫助大家更好的理解和使用spring框架,感興趣的朋友可以了解下2020-11-11