Java中的WeakHashMap源碼分析
1.WeakHashMap介紹
WeakHashMap是一種弱引用的map,底層數(shù)據(jù)結構為數(shù)組+鏈表,內部的key存儲為弱引用,在GC時如果key不存在強引用的情況下會被回收掉,而對于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的繼承關系上來看,可知其繼承AbstractMap,實現(xiàn)了Map接口。
其底層數(shù)據(jù)結構是Entry數(shù)組,Entry的數(shù)據(jù)結構如下:

從源碼上可知,Entry的內部并沒有存儲key的值,而是通過調用父類的構造方法,傳入key和ReferenceQueue,最終key和queue會關聯(lián)到Reference中
這里是GC時,清清除key的關鍵,這里大致看下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);
// 設置為守護線程
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}通過查看Reference源碼可知,在實例化時會創(chuàng)建一個守護線程,然后不斷循環(huán)將GC時的Entry入隊,關于如何清除value值的下面會進行分析。
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);
// 遍歷,是否要進行覆蓋操作
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];
// 構建鏈表,新元素在鏈表頭
tab[i] = new Entry<>(k, value, queue, h, e);
// 檢查是否需要擴容
if (++size >= threshold)
resize(tab.length * 2);
return null;
}分析:
WeakHashMap的put操作與HashMap相似,都會進行覆蓋操作(相同key),但是注意插入新節(jié)點是放在鏈表頭。
上述代碼中還要一個關鍵的函數(shù)getTable,后面會對其進行具體分析,先記下。
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);
// 獲取對應位置上的元素
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遍歷對應元素即可。
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一樣,則進行移除操作
if (h == e.hash && eq(k, e.get())) {
// 修改次數(shù)自增
modCount++;
// 元素個數(shù)自減
size--;
// 如果就是頭元素,則直接移除即可
if (prev == e)
tab[i] = next;
else
// 否則將前驅元素的next賦值為next,則將e移除
prev.next = next;
return e.value;
}
// 更新prev和e,繼續(xù)循環(huán)
prev = e;
e = next;
}
return null;
}分析:
移除元素操作的整體邏輯并不復雜,就是進行鏈表的常規(guī)操作,注意元素是鏈表頭時的特別處理,通過上述注釋,理解應該不困難。
resize操作(WeakHashMap的擴容操作)
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ù)轉移
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.
*/
// 確定擴容閾值
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
// 清除被GC的value
expungeStaleEntries();
// 數(shù)組轉移
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;
// 鏈式找元素
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的擴容函數(shù)中有點特別,因為key可能被GC掉,所以在擴容時也許要考慮這種情況,其他并沒有什么特別的,通過以上注釋理解應該不難。
在以上源碼分析中多次出現(xiàn)一個函數(shù):expungeStaleEntries
private void expungeStaleEntries() {
// 從隊列中取出被GC的Entry
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);
// 取出數(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
// 進行截斷
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的過程中入隊的。函數(shù)的邏輯并不復雜,通過上述注釋理解應該不難。
接下來看下該函數(shù)會在什么時候調用:

從以上調用鏈可知,在獲取size(獲取WeakHashMap的元素個數(shù))和resize(擴容時)會調用該函數(shù)清除被GC的key對應的value值。但還有一個getTable函數(shù)也會調用該函數(shù):

從以上調用鏈可知,在get/put等操作中都會調用expungeStaleEntries函數(shù)進GC后的收尾工作,其實WeakHashMap清除無強引用的核心也就是該函數(shù)了,因此理解該函數(shù)的作用是非常重要的。
3.總結
1.WeakHashMap非同步,默認容量為16,擴容因子默認為0.75,底層數(shù)據(jù)結構為Entry數(shù)組(數(shù)組+鏈表)。
2.WeakHashMap中的弱引用key會在下一次GC被清除,注意只會清除key,value會在每次map操作中清除。
3.在WeakHashMap中強引用的key是不會被GC清除的。
到此這篇關于Java中的WeakHashMap源碼分析的文章就介紹到這了,更多相關Java的WeakHashMap內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
關于mybatis-plus-generator的簡單使用示例詳解
在springboot項目中集成mybatis-plus是很方便開發(fā)的,最近看了一下plus的文檔,簡單用一下它的代碼生成器,接下來通過實例代碼講解關于mybatis-plus-generator的簡單使用,感興趣的朋友跟隨小編一起看看吧2024-03-03
基于java Springboot實現(xiàn)教務管理系統(tǒng)詳解
這篇文章主要介紹了Java 實現(xiàn)簡易教務管理系統(tǒng)的代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
spring Retryable注解實現(xiàn)重試詳解
這篇文章主要介紹了spring Retryable注解實現(xiàn)重試詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09

