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