Java的WeakHashMap源碼解析及使用場(chǎng)景詳解
WeakHashMap
目的
讓Map中不再使用的Entry被GC及時(shí)回收,釋放內(nèi)存空間
應(yīng)用場(chǎng)景-緩存
應(yīng)用場(chǎng)景:Map本身生命周期很長(zhǎng),需要長(zhǎng)期貯留內(nèi)存中,但Map中的Entry可以刪除,使用時(shí)可以從其它地方再次取得。
實(shí)例:tomcat中的緩存有用到。
源碼解析
先理解HashMap源碼
以及WeakReference:弱引用WeakReference所引用的對(duì)象的回收規(guī)則
WeahHashMap與HashMap在代碼實(shí)現(xiàn)上的不同點(diǎn):
- WeahHashMap類的 Entry<K, V> 繼承了 WeakReference,并設(shè)置 referent = key。
- WeakHashMap類內(nèi)定義了一個(gè)ReferenceQueue refQueue,每個(gè)entry創(chuàng)建時(shí),都會(huì)綁定這個(gè)refQueue,當(dāng)GC清理了entry的 referent 后,也就是說(shuō)entry與自己的key斷開(kāi)引用了,會(huì)將entry入隊(duì)到其綁定的 refQueue中去。 WeahkHashMap類內(nèi)的任何操作執(zhí)行前(如:get / size 等操作),都會(huì)先檢查一遍這個(gè)refQueue,將已經(jīng)被GC斷開(kāi)了對(duì)key的引用的entry全都從map中remove掉。
private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 定義一個(gè)隊(duì)列,GC會(huì)自動(dòng)將綁定到了此隊(duì)列的weak實(shí)例入隊(duì)到此隊(duì)列,用戶應(yīng)當(dāng)在每次訪問(wèn)weak實(shí)例前,都要檢查實(shí)例是否已經(jīng)被GC入隊(duì)到此隊(duì)列中,如果是,說(shuō)明實(shí)例已經(jīng)被GC,應(yīng)當(dāng)放棄使用。 ... private static class Entry<K,V> extends WeakHashMap<Object> implements Map.Entry<K,V>{ V value; int hash; Entry<K,V> next; Entry(Object key, V value, ReferenceQueue queue, int hash, Entry<K,V> next){ super(key, queue); // 調(diào)用父類WeakReference的構(gòu)造方法,設(shè)置referent = key, queue = refQueue; this.value = value; this.hash = hash; this.next = next; } }
具體如下:
1.在WeakHashMap類中定義了一個(gè)實(shí)例域ReferenceQueue<Map.Entry> queue。
/** * Reference queue for cleared WeakEntries */ private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
2.定義了一個(gè)內(nèi)部類WeakHashMap.Entry,直接繼承了WeakReference,Entry中沒(méi)有定義key字段,而是調(diào)用super(key,queue),將 key 保存在Reference類的referent字段中。
3.由于Entry本身對(duì)key是弱引用,因此GC會(huì)監(jiān)測(cè)key,在某個(gè)Entry的key處于適當(dāng)狀態(tài)時(shí),Entry會(huì)被加入到pending列表,然后由ReferenceHandler將Entry添加到queue隊(duì)列。
4.WeakHashMap中的許多操作,比如get(K key),size(),remove(K key)時(shí),都會(huì)先調(diào)用expungeStaleEntries();方法,這個(gè)方法會(huì)將已經(jīng)被添加到queue中的Entry從map中移除,同時(shí)會(huì)將entry的value變量的值置為null。
5.經(jīng)過(guò)步驟4,entry被從Map中移除后,不再有對(duì)此entry的引用,entry對(duì)key即referent的引用是弱引用,entry的value的值被賦值為null,原來(lái)的value的對(duì)象也不再被引用。GC就可以回收這些對(duì)象了。
代碼詳解
- 自定義的內(nèi)部類Entry<K,V>,實(shí)現(xiàn)了Map.Entry<K,V>,同時(shí)繼承了WeakReference。其referent指向key。也就是說(shuō),WeakHahsMap中的每個(gè)Entry都是一個(gè)weakRefer實(shí)例。
可以看到代碼中沒(méi)有定義實(shí)例域key,而是調(diào)用WeakReference的構(gòu)造函數(shù)super(key,queue),使得weakRefer實(shí)例的referent變量指向了key。
Entry的getKey()方法,就是調(diào)用WeakReference的get()方法,返回referent引用的key。
put(k, v)方法執(zhí)行,構(gòu)造Entry時(shí),會(huì)將給定的key賦值給referent。
get( k) 方法執(zhí)行時(shí),根據(jù) k.hash == entry.hash && k.equals(entry.get()) 來(lái)比較和查找,其中entry.get()得到的就是referent引用的key。
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; } @SuppressWarnings("unchecked") public K getKey() { return (K) WeakHashMap.unmaskNull(get()); } public int hashCode() { /* 重寫(xiě)實(shí)現(xiàn)了Object類中的hashCode,此方法是計(jì)算整個(gè)Entry實(shí)例對(duì)象的hashCode,不是計(jì)算key的hashCode */ K k = getKey(); V v = getValue(); return Objects.hashCode(k) ^ Objects.hashCode(v); } }
類中定義了一個(gè)聲明的同時(shí)也初始化了的ReferenceQueue類型的變量: private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
- WeakHashMap中的所有Entry的key都會(huì)在super(key,queue)時(shí),注冊(cè)到此queue上。GC線程會(huì)監(jiān)測(cè)這些key的可達(dá)性的狀態(tài),在key處于一個(gè)特殊狀態(tài)時(shí),就會(huì)將引用key的WeakReference實(shí)例對(duì)象的狀態(tài)設(shè)置為pending,并將WeakReference實(shí)例添加到pengding列表中去。而Reference類創(chuàng)建的ReferenceHandler線程則會(huì)自旋處理pending列表中的所有處于pending狀態(tài)的Reference實(shí)例,將它們enqueue()到queue中去,最終GC會(huì)回收queue里的所有Reference實(shí)例,由于是Entry實(shí)現(xiàn)了WeakReference,因此最終是整個(gè)entry被回收。
- 獲取WeakHashMap的table[]數(shù)組時(shí),會(huì)將已經(jīng)被GC入隊(duì)的key關(guān)聯(lián)的entry從map中刪除。
private Entry<K,V>[] getTable(){ Entry<K,V>[] table = expungeStaleEntries(); return table; } /** * Expunges stale entries from the table. */ 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; } } } }
get(K key)時(shí),會(huì)調(diào)用Reference的get()獲得Entry真正的key,與參數(shù)key做比較
public V get(Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int index = indexFor(h, tab.length); Entry<K,V> e = tab[index]; while (e != null) { if (e.hash == h && eq(k, e.get())) return e.value; e = e.next; } return null; }
實(shí)例解析:WeakHashMap在tomcat的緩存中的應(yīng)用
public final class ConcurrentCache<K,V>{ private final int size; private final Map<K,V> eden; //新創(chuàng)建的,最近使用的,放在eden里。 private final Map<K,V> longTerm; // 當(dāng)eden滿了后,將eden里的所有對(duì)象移動(dòng)到longTerm里。longTerm是一個(gè)WeakHashMap,GC及時(shí)清理其中的數(shù)據(jù)。 public ConcurrentCache(int size){ this.size=size; eden= new ConcurrentHashMap(size); longTerm = new WeakHashMap(); } public V get(K k){ /* 被get,最新被使用了,必須要在eden中*/ V v = eden.get(k) ; if(v==null){ synchronized(longTerm){ v = longTerm.get(k); } if(v!=null){ eden.put(k,v); } } return v; } public V put(K K,V v){ /*最新創(chuàng)建的,放到eden中*/ if(eden.size()>=size){ synchronized(longTerm){ longTerm.putAll(eden); } eden.clear(); } eden.put(k,v); } }
get時(shí),如果eden中沒(méi)有,而longTerm中有,則將數(shù)據(jù)取出后,再添加到eden中,保證最新最近使用的放在eden中。
put時(shí),如果eden已經(jīng)滿了,就將eden中的全部倒換到longTerm中去,將新創(chuàng)建的這個(gè)要put到eden中。
如此,longTerm中就是長(zhǎng)期未使用的、不常用的,因此用WeakHashMap以便GC回收,釋放空間。
緩存使用 ConcurrentHashMap 和 synchronized(longTerm) 很簡(jiǎn)單地實(shí)現(xiàn)了多線程安全的緩存。
到此這篇關(guān)于Java的WeakHashMap源碼解析及使用場(chǎng)景詳解的文章就介紹到這了,更多相關(guān)Java的WeakHashMap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+Druid開(kāi)啟監(jiān)控頁(yè)面的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot+Druid開(kāi)啟監(jiān)控頁(yè)面的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式
這篇文章主要介紹了Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06spring boot如何實(shí)現(xiàn)切割分片上傳
這篇文章主要介紹了spring boot如何實(shí)現(xiàn)切割分片上傳,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12Java concurrency之公平鎖(二)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency之公平鎖的第二篇內(nèi)容,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06java解析json復(fù)雜數(shù)據(jù)的方法詳解
這篇文章主要為大家詳細(xì)介紹了java解析json復(fù)雜數(shù)據(jù)的兩種常用方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以了解下2024-01-01Mybatis-plus 雙主鍵的實(shí)現(xiàn)示例
本文主要介紹了Mybatis-plus 雙主鍵的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05Java 編程如何使用 Class.forName() 加載類
在一些應(yīng)用中,無(wú)法事先知道使用者將加載什么類,而必須讓使用者指定類名稱以加載類,可以使用 Class的靜態(tài)forName()方法實(shí)現(xiàn)動(dòng)態(tài)加載類,這篇文章主要介紹了Java編程如何使用Class.forName()加載類,需要的朋友可以參考下2022-06-06Mybatis執(zhí)行插入語(yǔ)句后并返回主鍵ID問(wèn)題
這篇文章主要介紹了Mybatis執(zhí)行插入語(yǔ)句后并返回主鍵ID問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03