Java之WeakHashMap源碼淺析
定義
從名字可以得知主要和Map有關(guān),不過(guò)還有一個(gè)Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結(jié)構(gòu),因此想要徹底搞懂,我們還需要知道四種引用。
- 強(qiáng)引用:
- 如果一個(gè)對(duì)象具有強(qiáng)引用,它就不會(huì)被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足,JVM也不會(huì)回收它,而是拋出 OutOfMemoryError 錯(cuò)誤,使程序異常終止。 比如String str = "hello"這時(shí)候str就是一個(gè)強(qiáng)引用。
- 軟引用:
- 內(nèi)存足夠的時(shí)候,軟引用對(duì)象不會(huì)被回收,只有在內(nèi)存不足時(shí),系統(tǒng)則會(huì)回收軟引用對(duì)象,如果回收了軟引用對(duì)象之后仍然沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
- 弱引用:
- 如果一個(gè)對(duì)象具有弱引用,在垃圾回收時(shí)候,一旦發(fā)現(xiàn)弱引用對(duì)象,無(wú)論當(dāng)前內(nèi)存空間是否充足,都會(huì)將弱引用回收。
- 虛引用:
- 如果一個(gè)對(duì)象具有虛引用,就相當(dāng)于沒(méi)有引用,在任何時(shí)候都有可能被回收。 使用虛引用的目的就是為了得知對(duì)象被GC的時(shí)機(jī),所以可以利用虛引用來(lái)進(jìn)行銷毀前的一些操作,比如說(shuō)資源釋放等。
源碼解析
看下WeakHashMap 的構(gòu)造函數(shù)
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;
// 保證容量是2的整數(shù)倍,有助于hash運(yùn)算
while (capacity < initialCapacity)
capacity <<= 1;
// 初始化table數(shù)組
table = newTable(capacity);
this.loadFactor = loadFactor;
// 閥值
threshold = (int)(capacity * loadFactor);
}沒(méi)什么好說(shuō)的 table 是一個(gè) Entry數(shù)組 Entry<K,V>[] table; newTable會(huì)初始化一個(gè)數(shù)組數(shù)組的容量就是前面計(jì)算出來(lái)的capacity,其值為2的整數(shù)次方。
HashMap的容量為什么是2的n次方?HashMap是如何保證容量是2的n次方的? HashMap容量取2的n次方,主要與hash尋址有關(guān)。在put(key,value)時(shí),putVal()方法中通過(guò)i = (n - 1) & hash來(lái)計(jì)算key的散列地址。其實(shí),i = (n - 1) & hash是一個(gè)%操作。也就是說(shuō),HashMap是通過(guò)%運(yùn)算來(lái)獲得key的散列地址的。但是,%運(yùn)算的速度并沒(méi)有&的操作速度快。而&操作能代替%運(yùn)算,必須滿足一定的條件,也就是a%b=a&(b-1)僅當(dāng)b是2的n次方的時(shí)候方能成立。這也就是為什么HashMap的容量需要保持在2的n次方了。
再看下Entry的類定義
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;
}
...
}可以看到Entry是繼承WeakReference的,我們結(jié)合WeakReference再看一下:
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
* 創(chuàng)建一個(gè)新的弱應(yīng)用給傳入的對(duì)象,這個(gè)新的引用不注冊(cè)任何隊(duì)列
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
* 創(chuàng)建一個(gè)新的弱應(yīng)用給傳入的對(duì)象,這個(gè)新的引用注冊(cè)給一個(gè)給定的隊(duì)列
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}我們發(fā)現(xiàn)在 weakhashmap 中把key注冊(cè)給了 WeakReference ,也就是說(shuō)在 WeakHashMap 中key是一個(gè)弱引用。但這個(gè)queue是什么我們接著看,在往 WeakHashMap 中put一個(gè)元素的時(shí)候,會(huì)創(chuàng)建Entry。再看 WeakHashMap 的put操作,我們?nèi)绻煜?HashMap 其實(shí)我們不需要怎么看這部分的代碼,無(wú)非是計(jì)算hash值,散列分布到數(shù)組的各個(gè)位置,如果 hash 沖突使用拉鏈法進(jìn)行解決。這里和hashmap有一點(diǎn)不一樣的是hashmap如果鏈長(zhǎng)達(dá)到閥值會(huì)使用紅黑樹(shù)。
public V put(K key, V value) {
// 如果key是null則給定一個(gè)空的對(duì)象進(jìn)行修飾
Object k = maskNull(key);
// 計(jì)算key的hash
int h = hash(k);
// 獲取table
Entry<K,V>[] tab = getTable();
// 根據(jù)hash找到數(shù)組下標(biāo)
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, value, queue, h, e);
if (++size >= threshold) // 是否達(dá)到閥值達(dá)到閥值就擴(kuò)容
resize(tab.length * 2);
return null;
}可以看到這個(gè)queue是一個(gè)實(shí)例化final修飾的屬性。
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
再看下getTable是什么情況,看源碼會(huì)知道所有的WeekHashMap的所有操作都要調(diào)用 getTable -> expungeStaleEntries
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}我們看下expungeStaleEntries做了哪些事情?
private void expungeStaleEntries() {
// 從 ReferenceQueue中拉取元素
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
// 拿到entry的值賦值為null幫助GC
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}expungeStaleEntries 就是WeakHashMap的核心了,它承擔(dān)著Map中死對(duì)象的清理工作。原理就是依賴WeakReference和ReferenceQueue的特性。
在每個(gè)WeakHashMap都有個(gè)ReferenceQueue queue,在Entry初始化的時(shí)候也會(huì)將queue傳給WeakReference,這樣當(dāng)某個(gè)可以key失去所有強(qiáng)應(yīng)用之后,其key對(duì)應(yīng)的WeakReference對(duì)象會(huì)被放到queue里,有了queue就知道需要清理哪些Entry了。
這里也是整個(gè)WeakHashMap里唯一加了同步的地方。除了上文說(shuō)的到resize中調(diào)用了expungeStaleEntries(),size()中也調(diào)用了這個(gè)清理方法。另外 getTable()也調(diào)了,這就意味著幾乎所有其他方法都間接調(diào)用了清理。
WeakHashMap的一點(diǎn)點(diǎn)缺點(diǎn)
提到缺點(diǎn)我不太認(rèn)為是缺點(diǎn),在某種場(chǎng)景下缺點(diǎn)也有可能是優(yōu)點(diǎn),而且很多缺點(diǎn)也是可以彌補(bǔ)的。
但非要說(shuō)個(gè)一二三,這里列出下面兩種:
1.非線程安全
關(guān)鍵修改方法沒(méi)有提供任何同步,多線程環(huán)境下肯定會(huì)導(dǎo)致數(shù)據(jù)不一致的情況,所以使用時(shí)需要多注意。
2.單純作為Map沒(méi)有HashMap好
HashMap在Jdk8做了好多優(yōu)化,比如單鏈表在過(guò)長(zhǎng)時(shí)會(huì)轉(zhuǎn)化為紅黑樹(shù),降低極端情況下的操作復(fù)雜度。但WeakHashMap沒(méi)有相應(yīng)的優(yōu)化,有點(diǎn)像jdk8之前的HashMap版本。
WeakHashMap可以應(yīng)用的地方
1.緩存
2.診斷工具,比如atlas,將字節(jié)碼緩存放入到WeakHashMap中
到此這篇關(guān)于Java之WeakHashMap源碼淺析的文章就介紹到這了,更多相關(guān)WeakHashMap源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中實(shí)現(xiàn)數(shù)據(jù)字典的示例代碼
這篇文章主要介紹了SpringBoot中實(shí)現(xiàn)數(shù)據(jù)字典的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解
這篇文章主要介紹了Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09
教你用java?stream對(duì)集合中的對(duì)象按指定字段進(jìn)行分組并統(tǒng)計(jì)
這篇文章主要給大家介紹了關(guān)于用java?stream對(duì)集合中的對(duì)象按指定字段進(jìn)行分組并統(tǒng)計(jì)的相關(guān)資料,本文主要介紹了如何利用Java的Stream流來(lái)實(shí)現(xiàn)在list集合中,對(duì)具有相同name屬性的對(duì)象進(jìn)行匯總計(jì)算的需求,需要的朋友可以參考下2024-10-10
Java?properties?和?yml?的區(qū)別解析
properties和yml都是Spring?Boot支持的兩種配置文件,它們可以看做Spring?Boot在不同時(shí)期的兩種“產(chǎn)品”,這篇文章主要介紹了Java?properties?和?yml?的區(qū)別,需要的朋友可以參考下2023-02-02
spring(java,js,html) 截圖上傳圖片實(shí)例詳解
這篇文章主要介紹了spring(java,js,html) 截圖上傳圖片實(shí)例詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
Spring Boot集成starrocks快速入門Demo(適用場(chǎng)景)
StarRocks 是新一代極速全場(chǎng)景 MPP (Massively Parallel Processing) 數(shù)據(jù)庫(kù),StarRocks 的愿景是能夠讓用戶的數(shù)據(jù)分析變得更加簡(jiǎn)單和敏捷,這篇文章主要介紹了Spring Boot集成starrocks快速入門Demo,需要的朋友可以參考下2024-08-08
關(guān)于Mybatis 中使用Mysql存儲(chǔ)過(guò)程的方法
這篇文章給大家介紹了Mybatis 中使用Mysql存儲(chǔ)過(guò)程的方法,本文通過(guò)實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-03-03
Java進(jìn)程cpu頻繁100%問(wèn)題解決方案
這篇文章主要介紹了Java進(jìn)程cpu頻繁100%問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析
這篇文章主要為大家介紹了Netty分布式pipeline管道傳播事件總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03

