Java弱引用集合WeakHashMap總結
前言
WeakHashMap利用WeakReference的弱引用特性讓用戶在使用的過程中不會因為沒有釋放Map中的資源而導致內(nèi)存泄露。
WeakHashMap實現(xiàn)了Map接口,使用方式和其他的Map相同,需要注意的是get方法和size方法的使用。在介紹WeakHashMap之前需要先介紹一下Reference的概念。
1、Reference的實現(xiàn)
public abstract class Reference<T> { private T referent; //對應的引用對象 volatile ReferenceQueue<? super T> queue; //引用隊列,初始化的時候從外部傳入 Reference next; // 當引用對象可達性發(fā)生變化的時候,next會指向當前引用 transient private Reference<T> discovered; /* used by VM */ static private class Lock { } private static Lock lock = new Lock(); //用來同步鎖操作 }
Reference類有四個直接子類,PhantomReference、FinalReference、SoftReference、WeakReference。
其中SoftReference比WeakReference約束要強一些,當內(nèi)存不夠用的時候JVM才會將對應引用的對象刪除掉,而WeakReference在對象引用不可達的時候就會被JVM清理掉,PhantomReference(虛引用)和約束更弱,get方法永遠都返回null,無法像前兩者一樣可以通過get()方法獲取一個強引用,PhantomReference只能用來監(jiān)控對象的GC狀況。
無論哪種Reference,都有一個重要的對象來跟蹤對象的gc動作,這個就是ReferenceQueue。
2、ReferenceQueue的實現(xiàn)
public class ReferenceQueue { static private class Lock { }; //鎖對象,用來同步隊列操作 private Lock lock = new Lock(); private volatile Reference<? extends T> head = null; //頭結點 private long queueLength = 0; //隊列長度 }
入隊操作
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ synchronized (lock) { ReferenceQueue<?> queue = r.queue; //獲取reference之前的隊列,如果沒有綁定隊列,那就不需要入隊 if ((queue == NULL) || (queue == ENQUEUED)) { return false; } assert queue == this; r.queue = ENQUEUED; r.next = (head == null) ? r : head; //把r從鏈表的頭部插入 head = r; //頭結點指向r queueLength++; //隊列長度+1 if (r instanceof FinalReference) { sun.misc.VM.addFinalRefCount(1); } lock.notifyAll(); //喚醒刪除線程刪除頭結點 return true; } }
出隊列的操作相似,每個reference就是鏈表中的一個節(jié)點,next指向下一個reference節(jié)點。
刪除操作
public Reference<? extends T> remove(long timeout) throws IllegalArgumentException, InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("Negative timeout value"); } synchronized (lock) { Reference<? extends T> r = reallyPoll(); //嘗試獲取隊列的頭結點 if (r != null) return r; //獲取成功,返回 long start = (timeout == 0) ? 0 : System.nanoTime(); //如果超時時間不為0,就記錄一下當前的開始時間 for (;;) { lock.wait(timeout); r = reallyPoll(); if (r != null) return r; if (timeout != 0) { long end = System.nanoTime(); timeout -= (end - start) / 1000_000; //計算一下等待的時間 if (timeout <= 0) return null; //確認等待是否是超時,如果是就返回,否則就認為是有入隊請求喚醒當前線程,但是當前線程嘗試刪除頭結點失敗了(被其他線程刪除了),那么繼續(xù)嘗試刪除頭結點,再次執(zhí)行循環(huán)中的內(nèi)容直到超時 start = end; } } } }
ReferenceQueue是線程安全的,出隊入隊操作都由lock對象來保證線程安全,當用戶線程和jvm線程同時訪問ReferenceQueue的時候不會出現(xiàn)并發(fā)問題。
WeakHashMap
WeakHashMap內(nèi)部同樣是通過一個數(shù)組來實現(xiàn)存儲,解決沖突的方式也是使用拉鏈法,weakHashMap中重新定義了Entry類來存儲kv鍵值對,Entry的實現(xiàn)也是實現(xiàn)WeakHashMap特性的關鍵。
1、Entry的定義
WeakHashMap重新定義了一個entry,這個entry繼承了WeakReference類并且實現(xiàn)了Entry接口,使用該Entry存儲鍵值對不會產(chǎn)生強引用
jvm在垃圾回收的時候不會認為該引用是強引用,會正常的回收對象
Entry的定義如下
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; //存儲的value final int hash; //hash值 Entry<K,V> next; //拉鏈法解決沖突,形成單鏈表 Entry(Object key, V value,ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { super(key, queue); //這里的引用對象是key,跟蹤的是key對象的垃圾回收 this.value = value; this.hash = hash; this.next = next; } ... }
Entry的構造器需要傳入ReferenceQueue,這個queue就可以用來監(jiān)控全局的Entry被清理的情況。
2、清理操作
當對象被垃圾回收的時候,當前Map需要刪除掉對應的Entry,因為Entry此時指向的對象已經(jīng)被回收,所以需要找到被JVM回收的對象對應的Entry并且將Entry對象從Map中移除掉,實現(xiàn)方式是expungeStaleEntries方法
private void expungeStaleEntries() { 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,根據(jù)entry獲取entry鏈表在table中的索引 Entry<K,V> prev = table[i]; //找到entry鏈表的頭結點 Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; //找到對應節(jié)點 else prev.next = next; e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
該方法在map中的getTable()、size()和resize()方法中被調(diào)用,每次put或是get的時候都會先執(zhí)行getTable()方法,因此每次讀寫數(shù)據(jù)的時候都會清理掉無用的entry,所以用戶不會獲取到被垃圾回收的清理entry
因此,每次用戶調(diào)用get方法或是size方法的時候,都會觸發(fā)清理操作,所以每次返回的結果可能都不相同,因為內(nèi)部的entry持有的對象已經(jīng)被jvm回收。
線程安全問題
WeakHashMap作為容器本事不是線程安全的,但是在使用過程中,ReferenceQueue可能會被當前業(yè)務線程和JVM線程并發(fā)訪問,ReferenceQueue是線程安全的。
到此這篇關于Java弱引用集合WeakHashMap總結的文章就介紹到這了,更多相關WeakHashMap總結內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
spring @Scheduled注解的使用誤區(qū)及解決
這篇文章主要介紹了spring @Scheduled注解的使用誤區(qū)及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Java關于后端怎么去接收Date、LocalDateTime類型的參數(shù)詳解
這篇文章主要介紹了java關于后端怎么去接收Date、LocalDateTime類型的參數(shù),文中有詳細的代碼流程,對我們學習或工作有一定的參考價值,需要的朋友可以參考下2023-06-06