Java中的WeakHashMap詳解
楔子
WeakHashMap ,此種Map的特點(diǎn)是,當(dāng)除了自身有對(duì)key的引用外,此key沒(méi)有其他引用那么此map會(huì)自動(dòng)丟棄此值,所以比較適合做緩存。
WeakHashMap 的這種特性比較適合實(shí)現(xiàn)類(lèi)似本地、堆內(nèi)緩存的存儲(chǔ)機(jī)制——緩存的失效依賴(lài)于GC收集器的行為
WeakHashMap 的定義如下:
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
簡(jiǎn)單來(lái)說(shuō), WeakHashMap 實(shí)現(xiàn)了Map接口,基于 hash-table 實(shí)現(xiàn),在這種Map中,key的類(lèi)型是 WeakReference 。如果對(duì)應(yīng)的key被回收,則這個(gè)key指向的對(duì)象會(huì)被從Map容器中移除。
WeakHashMap 跟普通的HashMap不同,WeakHashMap的行為一定程度上基于垃圾收集器的行為,因此一些Map數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)的常識(shí)在WeakHashMap上會(huì)失效——size()方法的返回值會(huì)隨著程序的運(yùn)行變小,isEmpty()方法的返回值會(huì)從false變成true等等。
實(shí)例
此例子中聲明了兩個(gè)Map對(duì)象,一個(gè)是HashMap,一個(gè)是WeakHashMap,同時(shí)向兩個(gè)map中放入a、b兩個(gè)對(duì)象,當(dāng)HashMap remove掉a 并且將a、b都指向null時(shí),WeakHashMap中的a將自動(dòng)被回收掉。
出現(xiàn)這個(gè)狀況的原因是,對(duì)于a對(duì)象而言,當(dāng)HashMap remove掉并且將a指向null后,除了WeakHashMap中還保存a外已經(jīng)沒(méi)有指向a的指針了,所以WeakHashMap會(huì)自動(dòng)舍棄掉a,而對(duì)于b對(duì)象雖然指向了null,但HashMap中還有指向b的指針。
弱引用( WeakReference )的特性是:當(dāng)gc線(xiàn)程發(fā)現(xiàn)某個(gè)對(duì)象只有弱引用指向它,那么就會(huì)將其銷(xiāo)毀并回收內(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對(duì)象成為弱引用對(duì)象
System.gc(); //主動(dòng)觸發(fā)一次GC
await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);
典型使用場(chǎng)景: tomcat兩級(jí)緩存
tomcat的源碼里,實(shí)現(xiàn)緩存時(shí)會(huì)用到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,對(duì)jvm堆區(qū)有所了解的話(huà),可以猜測(cè)出tomcat在這里是使用 ConcurrentHashMap 和 WeakHashMap 做了 分代的緩存 。
- 在put方法里,在插入一個(gè)k-v時(shí),先檢查eden緩存的容量是不是超了。沒(méi)有超就直接放入eden緩存,如果超了則鎖定longterm將eden中所有的k-v都放入longterm。再將eden清空并插入k-v。
- 在get方法中,也是優(yōu)先從eden中找對(duì)應(yīng)的v,如果沒(méi)有則進(jìn)入longterm緩存中查找,找到后就加入eden緩存并返回。
經(jīng)過(guò)這樣的設(shè)計(jì),相對(duì)常用的對(duì)象都能在eden緩存中找到,不常用(有可能被銷(xiāo)毀的對(duì)象)的則進(jìn)入longterm緩存。而longterm的key的實(shí)際對(duì)象沒(méi)有其他引用指向它時(shí),gc就會(huì)自動(dòng)回收heap中該弱引用指向的實(shí)際對(duì)象,弱引用進(jìn)入引用隊(duì)列。longterm調(diào)用expungeStaleEntries()方法,遍歷引用隊(duì)列中的弱引用,并清除對(duì)應(yīng)的Entry,不會(huì)造成內(nèi)存空間的浪費(fèi)。
利用WeakHashMap實(shí)現(xiàn)內(nèi)存緩存
可以看出,WeakHashMap的這種特性比較適合實(shí)現(xiàn)類(lèi)似本地、堆內(nèi)緩存的存儲(chǔ)機(jī)制——緩存的失效依賴(lài)于GC收集器的行為。假設(shè)一種應(yīng)用場(chǎng)景:我們需要保存一批大的圖片對(duì)象,其中values是圖片的內(nèi)容,key是圖片的名字,這里我們需要選擇一種合適的容器保存這些對(duì)象。
使用普通的HashMap并不是好的選擇,這些大對(duì)象將會(huì)占用很多內(nèi)存,并且還不會(huì)被GC回收,除非我們?cè)趯?duì)應(yīng)的key廢棄之前主動(dòng)remove掉這些元素。WeakHashMap非常適合使用在這種場(chǎng)景下,下面的代碼演示了具體的實(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對(duì)象成為弱引用對(duì)象
System.gc(); //主動(dòng)觸發(fā)一次GC
await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);首先,創(chuàng)建一個(gè)WeakHashMap對(duì)象來(lái)存儲(chǔ)BigImage實(shí)例,對(duì)應(yīng)的key是UniqueImageName對(duì)象,保存到WeakHashMap里的時(shí)候,key是一個(gè)弱引用類(lèi)型。
然后,我們將imageName設(shè)置為null,這樣就沒(méi)有其他強(qiáng)引用指向bigImage對(duì)象,按照WeakHashMap的規(guī)則,在下一次GC周期中會(huì)回收bigImage對(duì)象。
通過(guò)System.gc()主動(dòng)觸發(fā)一次GC過(guò)程,然后可以發(fā)現(xiàn)WeakHashMap成為空的了。
強(qiáng)引用、軟引用和弱引用
- 強(qiáng)引用( Strong Reference )
- 軟引用( Soft Reference )
- 弱引用 ( WeakReference )
強(qiáng)引用
被強(qiáng)引用指向的對(duì)象,絕對(duì)不會(huì)被垃圾收集器回收。
Integer prime = 1;
這個(gè)語(yǔ)句中prime對(duì)象就有一個(gè)強(qiáng)引用。
軟引用
被 SoftReference 指向的對(duì)象可能會(huì)被垃圾收集器回收,但是只有在JVM內(nèi)存不夠的情況下才會(huì)回收;如下代碼可以創(chuàng)建一個(gè)軟引用:
Integer prime = 1; SoftReference<Integer> soft = new SoftReference<Integer>(prime); prime = null;
弱引用
當(dāng)一個(gè)對(duì)象僅僅被 WeakReference 引用時(shí),在下個(gè)垃圾收集周期時(shí)候該對(duì)象就會(huì)被回收。我們通過(guò)下面代碼創(chuàng)建一個(gè)WeakReference:
Integer prime = 1; WeakReference<Integer> soft = new WeakReference<Integer>(prime); prime = null;
當(dāng)把prime賦值為null的時(shí)候,原prime對(duì)象會(huì)在下一個(gè)垃圾收集周期中被回收,因?yàn)橐呀?jīng)沒(méi)有強(qiáng)引用指向它。
到此這篇關(guān)于Java中的WeakHashMap總結(jié)的文章就介紹到這了,更多相關(guān)Java的WeakHashMap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring framework體系結(jié)構(gòu)及模塊jar依賴(lài)關(guān)系詳解
在本篇文章里小編給大家整理的是關(guān)于spring framework體系結(jié)構(gòu)及模塊jar依賴(lài)關(guān)系,對(duì)此有興趣的朋友們可以學(xué)習(xí)下。2019-09-09
Java實(shí)現(xiàn)的猴子吃桃問(wèn)題算法示例
這篇文章主要介紹了Java實(shí)現(xiàn)的猴子吃桃問(wèn)題算法,簡(jiǎn)單描述了猴子吃桃問(wèn)題并結(jié)合實(shí)例形式給出了java解決猴子吃桃問(wèn)題的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10
Spring?Boot?中使用@KafkaListener并發(fā)批量接收消息的完整代碼
kakfa是我們?cè)陧?xiàng)目開(kāi)發(fā)中經(jīng)常使用的消息中間件。由于它的寫(xiě)性能非常高,因此,經(jīng)常會(huì)碰到讀取Kafka消息隊(duì)列時(shí)擁堵的情況,這篇文章主要介紹了Spring?Boot?中使用@KafkaListener并發(fā)批量接收消息,需要的朋友可以參考下2023-02-02
Java并發(fā)編程之工具類(lèi)Semaphore的使用
目前幾乎所有的語(yǔ)言都支持信號(hào)量機(jī)制,Java也不例外.Java中提供了Semaphore并發(fā)工具類(lèi)來(lái)支持信號(hào)量機(jī)制,下面我們就來(lái)了解Java實(shí)現(xiàn)的信號(hào)量機(jī)制,文中有非常詳細(xì)的介紹,需要的朋友可以參考下2021-06-06
Java字符串格式化功能?String.format用法詳解
String類(lèi)的format()方法用于創(chuàng)建格式化的字符串以及連接多個(gè)字符串對(duì)象,熟悉C語(yǔ)言的同學(xué)應(yīng)該記得C語(yǔ)言的sprintf()方法,兩者有類(lèi)似之處,format()方法有兩種重載形式2024-09-09
解決java調(diào)用python代碼返回值中文亂碼問(wèn)題
這篇文章主要介紹了解決java調(diào)用python代碼返回值中文亂碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot如何訪(fǎng)問(wèn)不同的數(shù)據(jù)庫(kù)的方法實(shí)現(xiàn)
本文主要介紹了在SpringBoot應(yīng)用中配置和管理多個(gè)數(shù)據(jù)源的方法,包括使用SpringBoot官方支持的配置方式和第三方庫(kù)實(shí)現(xiàn)多數(shù)據(jù)源配置,感興趣的可以了解一下2024-11-11

