Java中的WeakHashMap詳解
楔子
WeakHashMap ,此種Map的特點(diǎn)是,當(dāng)除了自身有對key的引用外,此key沒有其他引用那么此map會自動(dòng)丟棄此值,所以比較適合做緩存。
WeakHashMap 的這種特性比較適合實(shí)現(xiàn)類似本地、堆內(nèi)緩存的存儲機(jī)制——緩存的失效依賴于GC收集器的行為
WeakHashMap 的定義如下:
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
簡單來說, WeakHashMap 實(shí)現(xiàn)了Map接口,基于 hash-table 實(shí)現(xiàn),在這種Map中,key的類型是 WeakReference 。如果對應(yīng)的key被回收,則這個(gè)key指向的對象會被從Map容器中移除。
WeakHashMap 跟普通的HashMap不同,WeakHashMap的行為一定程度上基于垃圾收集器的行為,因此一些Map數(shù)據(jù)結(jié)構(gòu)對應(yīng)的常識在WeakHashMap上會失效——size()方法的返回值會隨著程序的運(yùn)行變小,isEmpty()方法的返回值會從false變成true等等。
實(shí)例
此例子中聲明了兩個(gè)Map對象,一個(gè)是HashMap,一個(gè)是WeakHashMap,同時(shí)向兩個(gè)map中放入a、b兩個(gè)對象,當(dāng)HashMap remove掉a 并且將a、b都指向null時(shí),WeakHashMap中的a將自動(dòng)被回收掉。
出現(xiàn)這個(gè)狀況的原因是,對于a對象而言,當(dāng)HashMap remove掉并且將a指向null后,除了WeakHashMap中還保存a外已經(jīng)沒有指向a的指針了,所以WeakHashMap會自動(dòng)舍棄掉a,而對于b對象雖然指向了null,但HashMap中還有指向b的指針。
弱引用( WeakReference )的特性是:當(dāng)gc線程發(fā)現(xiàn)某個(gè)對象只有弱引用指向它,那么就會將其銷毀并回收內(nèi)存。
WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>(); BigImage bigImage = new BigImage("image_id"); UniqueImageName imageName = new UniqueImageName("name_of_big_image"); //強(qiáng)引用 map.put(imageName, bigImage); assertTrue(map.containsKey(imageName)); imageName = null; //map中的values對象成為弱引用對象 System.gc(); //主動(dòng)觸發(fā)一次GC await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);
典型使用場景: tomcat兩級緩存
tomcat的源碼里,實(shí)現(xiàn)緩存時(shí)會用到WeakHashMap
package org.apache.tomcat.util.collections; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; public final class ConcurrentCache<K,V> { private final int size; private final Map<K,V> eden; private final Map<K,V> longterm; public ConcurrentCache(int size) { this.size = size; this.eden = new ConcurrentHashMap<>(size); this.longterm = new WeakHashMap<>(size); } public V get(K k) { V v = this.eden.get(k); if (v == null) { synchronized (longterm) { v = this.longterm.get(k); } if (v != null) { this.eden.put(k, v); } } return v; } public void put(K k, V v) { if (this.eden.size() >= size) { synchronized (longterm) { this.longterm.putAll(this.eden); } this.eden.clear(); } this.eden.put(k, v); } }
源碼中有 eden 和 longterm 的兩個(gè)map,對jvm堆區(qū)有所了解的話,可以猜測出tomcat在這里是使用 ConcurrentHashMap 和 WeakHashMap 做了 分代的緩存 。
- 在put方法里,在插入一個(gè)k-v時(shí),先檢查eden緩存的容量是不是超了。沒有超就直接放入eden緩存,如果超了則鎖定longterm將eden中所有的k-v都放入longterm。再將eden清空并插入k-v。
- 在get方法中,也是優(yōu)先從eden中找對應(yīng)的v,如果沒有則進(jìn)入longterm緩存中查找,找到后就加入eden緩存并返回。
經(jīng)過這樣的設(shè)計(jì),相對常用的對象都能在eden緩存中找到,不常用(有可能被銷毀的對象)的則進(jìn)入longterm緩存。而longterm的key的實(shí)際對象沒有其他引用指向它時(shí),gc就會自動(dòng)回收heap中該弱引用指向的實(shí)際對象,弱引用進(jìn)入引用隊(duì)列。longterm調(diào)用expungeStaleEntries()方法,遍歷引用隊(duì)列中的弱引用,并清除對應(yīng)的Entry,不會造成內(nèi)存空間的浪費(fèi)。
利用WeakHashMap實(shí)現(xiàn)內(nèi)存緩存
可以看出,WeakHashMap的這種特性比較適合實(shí)現(xiàn)類似本地、堆內(nèi)緩存的存儲機(jī)制——緩存的失效依賴于GC收集器的行為。假設(shè)一種應(yīng)用場景:我們需要保存一批大的圖片對象,其中values是圖片的內(nèi)容,key是圖片的名字,這里我們需要選擇一種合適的容器保存這些對象。
使用普通的HashMap并不是好的選擇,這些大對象將會占用很多內(nèi)存,并且還不會被GC回收,除非我們在對應(yīng)的key廢棄之前主動(dòng)remove掉這些元素。WeakHashMap非常適合使用在這種場景下,下面的代碼演示了具體的實(shí)現(xiàn):
WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>(); BigImage bigImage = new BigImage("image_id"); UniqueImageName imageName = new UniqueImageName("name_of_big_image"); //強(qiáng)引用 map.put(imageName, bigImage); assertTrue(map.containsKey(imageName)); imageName = null; //map中的values對象成為弱引用對象 System.gc(); //主動(dòng)觸發(fā)一次GC await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);
首先,創(chuàng)建一個(gè)WeakHashMap對象來存儲BigImage實(shí)例,對應(yīng)的key是UniqueImageName對象,保存到WeakHashMap里的時(shí)候,key是一個(gè)弱引用類型。
然后,我們將imageName設(shè)置為null,這樣就沒有其他強(qiáng)引用指向bigImage對象,按照WeakHashMap的規(guī)則,在下一次GC周期中會回收bigImage對象。
通過System.gc()主動(dòng)觸發(fā)一次GC過程,然后可以發(fā)現(xiàn)WeakHashMap成為空的了。
強(qiáng)引用、軟引用和弱引用
- 強(qiáng)引用( Strong Reference )
- 軟引用( Soft Reference )
- 弱引用 ( WeakReference )
強(qiáng)引用
被強(qiáng)引用指向的對象,絕對不會被垃圾收集器回收。
Integer prime = 1;
這個(gè)語句中prime對象就有一個(gè)強(qiáng)引用。
軟引用
被 SoftReference 指向的對象可能會被垃圾收集器回收,但是只有在JVM內(nèi)存不夠的情況下才會回收;如下代碼可以創(chuàng)建一個(gè)軟引用:
Integer prime = 1; SoftReference<Integer> soft = new SoftReference<Integer>(prime); prime = null;
弱引用
當(dāng)一個(gè)對象僅僅被 WeakReference 引用時(shí),在下個(gè)垃圾收集周期時(shí)候該對象就會被回收。我們通過下面代碼創(chuàng)建一個(gè)WeakReference:
Integer prime = 1; WeakReference<Integer> soft = new WeakReference<Integer>(prime); prime = null;
當(dāng)把prime賦值為null的時(shí)候,原prime對象會在下一個(gè)垃圾收集周期中被回收,因?yàn)橐呀?jīng)沒有強(qiáng)引用指向它。
到此這篇關(guān)于Java中的WeakHashMap總結(jié)的文章就介紹到這了,更多相關(guān)Java的WeakHashMap內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java內(nèi)部類的實(shí)現(xiàn)原理與可能的內(nèi)存泄漏說明
這篇文章主要介紹了Java內(nèi)部類的實(shí)現(xiàn)原理與可能的內(nèi)存泄漏說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10如何對Mysql數(shù)據(jù)表查詢出來的結(jié)果進(jìn)行排序
這篇文章主要介紹了如何對Mysql數(shù)據(jù)表查詢出來的結(jié)果進(jìn)行排序問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08Spring Boot集成Redis實(shí)戰(zhàn)操作功能
這篇文章主要介紹了Spring Boot集成Redis實(shí)戰(zhàn)操作,包括如何集成redis以及redis的一些優(yōu)點(diǎn),本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11學(xué)習(xí)Java正則表達(dá)式(匹配、替換、查找)
這篇文章主要介紹了Java正則表達(dá)式的匹配、替換、查找和切割等操作,對于正則表達(dá)式的匹配、替換大家已經(jīng)不陌生了吧2015-12-12