Java之WeakHashMap源碼淺析
定義
從名字可以得知主要和Map有關(guān),不過還有一個(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ì)象之后仍然沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
- 弱引用:
- 如果一個(gè)對(duì)象具有弱引用,在垃圾回收時(shí)候,一旦發(fā)現(xiàn)弱引用對(duì)象,無論當(dāng)前內(nèi)存空間是否充足,都會(huì)將弱引用回收。
- 虛引用:
- 如果一個(gè)對(duì)象具有虛引用,就相當(dāng)于沒有引用,在任何時(shí)候都有可能被回收。 使用虛引用的目的就是為了得知對(duì)象被GC的時(shí)機(jī),所以可以利用虛引用來進(jìn)行銷毀前的一些操作,比如說資源釋放等。
源碼解析
看下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); }
沒什么好說的 table 是一個(gè) Entry數(shù)組 Entry<K,V>[] table; newTable會(huì)初始化一個(gè)數(shù)組數(shù)組的容量就是前面計(jì)算出來的capacity,其值為2的整數(shù)次方。
HashMap的容量為什么是2的n次方?HashMap是如何保證容量是2的n次方的? HashMap容量取2的n次方,主要與hash尋址有關(guān)。在put(key,value)時(shí),putVal()方法中通過i = (n - 1) & hash來計(jì)算key的散列地址。其實(shí),i = (n - 1) & hash是一個(gè)%操作。也就是說,HashMap是通過%運(yùn)算來獲得key的散列地址的。但是,%運(yùn)算的速度并沒有&的操作速度快。而&操作能代替%運(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 ,也就是說在 WeakHashMap 中key是一個(gè)弱引用。但這個(gè)queue是什么我們接著看,在往 WeakHashMap 中put一個(gè)元素的時(shí)候,會(huì)創(chuàng)建Entry。再看 WeakHashMap 的put操作,我們?nèi)绻煜?HashMap 其實(shí)我們不需要怎么看這部分的代碼,無非是計(jì)算hash值,散列分布到數(shù)組的各個(gè)位置,如果 hash 沖突使用拉鏈法進(jìn)行解決。這里和hashmap有一點(diǎn)不一樣的是hashmap如果鏈長達(dá)到閥值會(huì)使用紅黑樹。
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里唯一加了同步的地方。除了上文說的到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),在某種場景下缺點(diǎn)也有可能是優(yōu)點(diǎn),而且很多缺點(diǎn)也是可以彌補(bǔ)的。
但非要說個(gè)一二三,這里列出下面兩種:
1.非線程安全
關(guān)鍵修改方法沒有提供任何同步,多線程環(huán)境下肯定會(huì)導(dǎo)致數(shù)據(jù)不一致的情況,所以使用時(shí)需要多注意。
2.單純作為Map沒有HashMap好
HashMap在Jdk8做了好多優(yōu)化,比如單鏈表在過長時(shí)會(huì)轉(zhuǎn)化為紅黑樹,降低極端情況下的操作復(fù)雜度。但WeakHashMap沒有相應(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ù)字典的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java 獲取本機(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流來實(shí)現(xiàn)在list集合中,對(duì)具有相同name屬性的對(duì)象進(jìn)行匯總計(jì)算的需求,需要的朋友可以參考下2024-10-10Java?properties?和?yml?的區(qū)別解析
properties和yml都是Spring?Boot支持的兩種配置文件,它們可以看做Spring?Boot在不同時(shí)期的兩種“產(chǎn)品”,這篇文章主要介紹了Java?properties?和?yml?的區(qū)別,需要的朋友可以參考下2023-02-02spring(java,js,html) 截圖上傳圖片實(shí)例詳解
這篇文章主要介紹了spring(java,js,html) 截圖上傳圖片實(shí)例詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Spring Boot集成starrocks快速入門Demo(適用場景)
StarRocks 是新一代極速全場景 MPP (Massively Parallel Processing) 數(shù)據(jù)庫,StarRocks 的愿景是能夠讓用戶的數(shù)據(jù)分析變得更加簡單和敏捷,這篇文章主要介紹了Spring Boot集成starrocks快速入門Demo,需要的朋友可以參考下2024-08-08關(guān)于Mybatis 中使用Mysql存儲(chǔ)過程的方法
這篇文章給大家介紹了Mybatis 中使用Mysql存儲(chǔ)過程的方法,本文通過實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-03-03Netty分布式pipeline管道傳播事件的邏輯總結(jié)分析
這篇文章主要為大家介紹了Netty分布式pipeline管道傳播事件總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03