Java中的WeakHashMap源碼分析
1.WeakHashMap介紹
WeakHashMap是一種弱引用的map,底層數(shù)據(jù)結(jié)構(gòu)為數(shù)組+鏈表,內(nèi)部的key存儲為弱引用,在GC時如果key不存在強(qiáng)引用的情況下會被回收掉,而對于value的回收會在下一次操作map時回收掉,所以WeakHashMap適合緩存處理。
1 java.lang.Object 2 java.util.AbstractMap<K, V> 3 java.util.WeakHashMap<K, V> 4 5 public class WeakHashMap<K,V> 6 extends AbstractMap<K,V> 7 implements Map<K,V> {}
從WeakHashMap的繼承關(guān)系上來看,可知其繼承AbstractMap,實(shí)現(xiàn)了Map接口。
其底層數(shù)據(jù)結(jié)構(gòu)是Entry數(shù)組,Entry的數(shù)據(jù)結(jié)構(gòu)如下:
從源碼上可知,Entry的內(nèi)部并沒有存儲key的值,而是通過調(diào)用父類的構(gòu)造方法,傳入key和ReferenceQueue,最終key和queue會關(guān)聯(lián)到Reference中
這里是GC時,清清除key的關(guān)鍵,這里大致看下Reference的源碼:
private static class ReferenceHandler extends Thread { private static void ensureClassInitialized(Class<?> clazz) { try { Class.forName(clazz.getName(), true, clazz.getClassLoader()); } catch (ClassNotFoundException e) { throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); } } static { // pre-load and initialize InterruptedException and Cleaner classes // so that we don't get into trouble later in the run loop if there's // memory shortage while loading/initializing them lazily. ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { // 注意這里為一個死循環(huán) while (true) { tryHandlePending(true); } } } static boolean tryHandlePending(boolean waitForNotify) { Reference<Object> r; Cleaner c; try { synchronized (lock) { if (pending != null) { r = pending; // 'instanceof' might throw OutOfMemoryError sometimes // so do this before un-linking 'r' from the 'pending' chain... c = r instanceof Cleaner ? (Cleaner) r : null; // unlink 'r' from 'pending' chain pending = r.discovered; r.discovered = null; } else { // The waiting on the lock may cause an OutOfMemoryError // because it may try to allocate exception objects. if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } } catch (OutOfMemoryError x) { // Give other threads CPU time so they hopefully drop some live references // and GC reclaims some space. // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above // persistently throws OOME for some time... Thread.yield(); // retry return true; } catch (InterruptedException x) { // retry return true; } // Fast path for cleaners if (c != null) { c.clean(); return true; } // 加入對列 ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; } static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); // 創(chuàng)建handler Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ // 線程優(yōu)先級最大 handler.setPriority(Thread.MAX_PRIORITY); // 設(shè)置為守護(hù)線程 handler.setDaemon(true); handler.start(); // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); }
通過查看Reference源碼可知,在實(shí)例化時會創(chuàng)建一個守護(hù)線程,然后不斷循環(huán)將GC時的Entry入隊(duì),關(guān)于如何清除value值的下面會進(jìn)行分析。
2.具體源碼分析
put操作:
public V put(K key, V value) { // 確定key值,允許key為null Object k = maskNull(key); // 獲取器hash值 int h = hash(k); // 獲取tab Entry<K,V>[] tab = getTable(); // 確定在tab中的位置 簡單的&操作 int i = indexFor(h, tab.length); // 遍歷,是否要進(jìn)行覆蓋操作 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; } } // 修改次數(shù)自增 modCount++; // 取出i上的元素 Entry<K,V> e = tab[i]; // 構(gòu)建鏈表,新元素在鏈表頭 tab[i] = new Entry<>(k, value, queue, h, e); // 檢查是否需要擴(kuò)容 if (++size >= threshold) resize(tab.length * 2); return null; }
分析:
WeakHashMap的put操作與HashMap相似,都會進(jìn)行覆蓋操作(相同key),但是注意插入新節(jié)點(diǎn)是放在鏈表頭。
上述代碼中還要一個關(guān)鍵的函數(shù)getTable,后面會對其進(jìn)行具體分析,先記下。
get操作:
public V get(Object key) { // 確定key Object k = maskNull(key); // 計算其hashCode int h = hash(k); Entry<K,V>[] tab = getTable(); int index = indexFor(h, tab.length); // 獲取對應(yīng)位置上的元素 Entry<K,V> e = tab[index]; while (e != null) { // 如果hashCode相同,并且key也相同,則返回,否則繼續(xù)循環(huán) if (e.hash == h && eq(k, e.get())) return e.value; e = e.next; } // 未找到,則返回null return null; }
分析:
get操作邏輯簡單,根據(jù)key遍歷對應(yīng)元素即可。
remove操作:
public V remove(Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); // 數(shù)組上第一個元素 Entry<K,V> prev = tab[i]; Entry<K,V> e = prev; // 循環(huán) while (e != null) { Entry<K,V> next = e.next; // 如果hash值相同,并且key一樣,則進(jìn)行移除操作 if (h == e.hash && eq(k, e.get())) { // 修改次數(shù)自增 modCount++; // 元素個數(shù)自減 size--; // 如果就是頭元素,則直接移除即可 if (prev == e) tab[i] = next; else // 否則將前驅(qū)元素的next賦值為next,則將e移除 prev.next = next; return e.value; } // 更新prev和e,繼續(xù)循環(huán) prev = e; e = next; } return null; }
分析:
移除元素操作的整體邏輯并不復(fù)雜,就是進(jìn)行鏈表的常規(guī)操作,注意元素是鏈表頭時的特別處理,通過上述注釋,理解應(yīng)該不困難。
resize操作(WeakHashMap的擴(kuò)容操作)
void resize(int newCapacity) { Entry<K,V>[] oldTable = getTable(); // 原數(shù)組長度 int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 創(chuàng)建新的數(shù)組 Entry<K,V>[] newTable = newTable(newCapacity); // 數(shù)據(jù)轉(zhuǎn)移 transfer(oldTable, newTable); table = newTable; /* * If ignoring null elements and processing ref queue caused massive * shrinkage, then restore old table. This should be rare, but avoids * unbounded expansion of garbage-filled tables. */ // 確定擴(kuò)容閾值 if (size >= threshold / 2) { threshold = (int)(newCapacity * loadFactor); } else { // 清除被GC的value expungeStaleEntries(); // 數(shù)組轉(zhuǎn)移 transfer(newTable, oldTable); table = oldTable; } } private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) { // 遍歷原數(shù)組 for (int j = 0; j < src.length; ++j) { // 取出元素 Entry<K,V> e = src[j]; src[j] = null; // 鏈?zhǔn)秸以? while (e != null) { Entry<K,V> next = e.next; Object key = e.get(); // key被回收的情況 if (key == null) { e.next = null; // Help GC e.value = null; // " " size--; } else { // 確定在新數(shù)組的位置 int i = indexFor(e.hash, dest.length); // 插入元素 注意這里為頭插法,會倒序 e.next = dest[i]; dest[i] = e; } e = next; } } }
分析:
WeakHashMap的擴(kuò)容函數(shù)中有點(diǎn)特別,因?yàn)閗ey可能被GC掉,所以在擴(kuò)容時也許要考慮這種情況,其他并沒有什么特別的,通過以上注釋理解應(yīng)該不難。
在以上源碼分析中多次出現(xiàn)一個函數(shù):expungeStaleEntries
private void expungeStaleEntries() { // 從隊(duì)列中取出被GC的Entry for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) x; // 確定元素在隊(duì)列中的位置 int i = indexFor(e.hash, table.length); // 取出數(shù)組中的第一個元素 prev Entry<K,V> prev = table[i]; Entry<K,V> p = prev; // 循環(huán) while (p != null) { Entry<K,V> next = p.next; // 找到 if (p == e) { // 判斷是否是鏈表頭元素 第一次時 if (prev == e) // 將next直接掛在i位置 table[i] = next; else // 進(jìn)行截斷 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 prev = p; p = next; } } } }
分析:
該函數(shù)的主要作用就是清除Entry的value,該Entry是在GC清除key的過程中入隊(duì)的。函數(shù)的邏輯并不復(fù)雜,通過上述注釋理解應(yīng)該不難。
接下來看下該函數(shù)會在什么時候調(diào)用:
從以上調(diào)用鏈可知,在獲取size(獲取WeakHashMap的元素個數(shù))和resize(擴(kuò)容時)會調(diào)用該函數(shù)清除被GC的key對應(yīng)的value值。但還有一個getTable函數(shù)也會調(diào)用該函數(shù):
從以上調(diào)用鏈可知,在get/put等操作中都會調(diào)用expungeStaleEntries函數(shù)進(jìn)GC后的收尾工作,其實(shí)WeakHashMap清除無強(qiáng)引用的核心也就是該函數(shù)了,因此理解該函數(shù)的作用是非常重要的。
3.總結(jié)
1.WeakHashMap非同步,默認(rèn)容量為16,擴(kuò)容因子默認(rèn)為0.75,底層數(shù)據(jù)結(jié)構(gòu)為Entry數(shù)組(數(shù)組+鏈表)。
2.WeakHashMap中的弱引用key會在下一次GC被清除,注意只會清除key,value會在每次map操作中清除。
3.在WeakHashMap中強(qiáng)引用的key是不會被GC清除的。
到此這篇關(guān)于Java中的WeakHashMap源碼分析的文章就介紹到這了,更多相關(guān)Java的WeakHashMap內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于mybatis-plus-generator的簡單使用示例詳解
在springboot項(xiàng)目中集成mybatis-plus是很方便開發(fā)的,最近看了一下plus的文檔,簡單用一下它的代碼生成器,接下來通過實(shí)例代碼講解關(guān)于mybatis-plus-generator的簡單使用,感興趣的朋友跟隨小編一起看看吧2024-03-03基于java Springboot實(shí)現(xiàn)教務(wù)管理系統(tǒng)詳解
這篇文章主要介紹了Java 實(shí)現(xiàn)簡易教務(wù)管理系統(tǒng)的代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08spring Retryable注解實(shí)現(xiàn)重試詳解
這篇文章主要介紹了spring Retryable注解實(shí)現(xiàn)重試詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09Java實(shí)現(xiàn)更新順序表中的指定元素的示例
本文主要介紹了Java實(shí)現(xiàn)更新順序表中的指定元素的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06