本文會經過對弱引用的定義講起,而後經過案例的使用一步一步的深刻源碼進行分析其原理,從而讓讀者深入的理解什麼是弱引用,如何使用弱引用,什麼場景下會使用弱引用,弱引用能夠解決什麼樣的問題,以及它的源碼實現是怎樣的,其中會涉及的內存溢出,垃圾回收原理html
弱引用主要應用在不阻止它的key或者value 被回收的mapping。直接貼英文吧,翻譯水平有限(weak references are for implementing canonicalizing mappings that do not prevent their keys (or values) from being reclaimed)java
弱引用的出現就是爲了垃圾回收服務的。它引用一個對象,可是並不阻止該對象被回收。若是使用一個強引用的話,只要該引用存在,那麼被引用的對象是不能被回收的。弱引用則沒有這個問題。在垃圾回收器運行的時候,若是一個對象的全部引用都是弱引用的話,該對象會被回收程序員
理想的狀況下,咱們但願當咱們再也不使用一個對象的時候,可以在gc 發生的時候就把它回收掉。可是有些時候,因爲咱們的粗忽,在壞的狀況下會致使內存溢出。這種案例尤爲發生在一個生命使用週期很長的map 存放了不少實際使用生命週期短的對象。請看下面這個例子面試
public class StrongRefenceDemo { static Map<String, String> map; public static void main(String[] args) throws Exception { StrongRefenceDemo demo = new StrongRefenceDemo(); demo.strongTest(); System.out.println("gc 發生前:" + map.size()); System.out.println("開始通知GC"); //注意,這裏只是經過垃圾回收器進行垃圾回收,並不必定立刻執行 System.gc(); Thread.sleep(1000 * 5); System.out.println("gc 發生後:" + map.size()); } /** * 強引用測試 */ public void strongTest() { map = new HashMap<>(); String key = new String("key"); String value = new String("value"); map.put(key, value); key = null; value = null; } }
運行後輸出結果:小程序
gc 發生前:1 開始通知GC gc 發生後:1
從輸出的結果能夠看到,即便咱們經過把key和value 設置爲null 來告訴jvm,咱們再也不使用這個對象了,map 裏面對象依然沒有被GC 回收,由於key和value 被一個強引用map 指向,根據可達性判斷,垃圾回收器是不能回收掉key和value 這個對象的。map 被定義爲statis 的靜態變量,是一個使用生命週期很長的對象。在strongTest()方法中存在了一個key和value 的局部變量,它隨着方法的執行完,這個變量的生命使用週期就結束了,可是粗糙的程序員忘記remove 了,這個時候垃圾回收器是不能回收它的。若是這種生命週期相對短的對象不少,最終就有可能消耗掉JVM中所有的內存。api
可是這裏我有一個好奇,假如這裏的key和value 指向的對象在執行完strongTest()方法 之後用不着了,可是我可能又不是很好的判斷去主動調用remove 來移除它。想要垃圾回收器本身判斷回收掉可不能夠呢?答案實際上是能夠的,這個時候就是弱引用上場了,請看下面程序數組
public class WeakRefenceDemo { static Map<WeakReference<String>, String> map; public static void main(String[] args) throws Exception { WeakRefenceDemo demo = new WeakRefenceDemo(); demo.weakTest(); System.out.println("gc 發生前:" + map.size()); System.out.println("開始通知GC"); //注意,這裏只是經過垃圾回收器進行垃圾回收,並不必定立刻執行 System.gc(); Thread.sleep(1000 * 5); System.out.println("gc 發生後:" + map.size()); } /** * 若引用測試 */ public void weakTest() { map = new WeakHashMap<>(); String key = new String("key"); String value = new String("value"); map.put(new WeakReference<>(key), value); key = null; value = null; } }
運行上面代碼輸出結果安全
gc 發生前:1 開始通知GC gc 發生後:0
從輸出結果0,咱們能夠判斷已經成功被垃圾回收了。what?整個過程咱們只是把HashMap 換成了WeakHashMap,而且key 由String 換成了WeakReference<String>。其實就是因爲字符串只有弱引用指向,因此能夠被垃圾回收掉。是否是很簡單,若是到這裏你就中止研究弱引用了,那就太暴殄天物了數據結構
上面的程序片斷中,其實只有key 設置了爲弱引用new WeakReference<>(key),那正常也就只有這個key 對應的內存被回收而已,因爲沒有調用remove ,裏面的value 和entry 也是不會回收掉的,那爲何最後輸出的size 是0 呢?
很好的問題,咱們深刻去看WeakHashMap 的源碼,咱們發現了一個神奇的方法expungeStaleEntries()。在看源碼以前先解析下引用隊列的概念:
在弱引用被回收的時候會把該對象放到引用隊列中,也就意味着從引用隊列中獲取的對象都是被回收的對象,先解釋到這裏,足以知足咱們下面的源碼分析了,接下來會作詳細的解析oracle
/** * Expunges stale entries from the table. */ 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<K,V> prev = table[i]; Entry<K,V> p = prev; //下面就是經過遍歷鏈表來設置值爲null 來告訴垃圾回收器回收掉 //注意WeakHashMap 和HashMap 的數據結構都是經過數組+鏈表組成的,只有理解了這點才知道下面的代碼作了什麼 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 //這裏經過設置value 爲null 來告訴垃圾回收 e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
從上面的代碼片斷,大概的意思就是從引用隊列裏面取出被回收的對象,而後和WeakHashMap 中的對象查找,找到以後就把對應的value 也設置爲null,而且把對應的entry 設置爲null,來告訴GC 去回收它。從源碼能夠看到expungeStaleEntries() 這個方法在執行WeakHashMap中的任何方法的時候都會被調用到的
/** * Returns the table after first expunging stale entries. */ private Entry<K,V>[] getTable() { //被調用 expungeStaleEntries(); return table; } /** * Returns the number of key-value mappings in this map. * This result is a snapshot, and may not reflect unprocessed * entries that will be removed before next attempted access * because they are no longer referenced. */ public int size() { if (size == 0) return 0; //被調用 expungeStaleEntries(); return size; }
到這裏也就徹底明白爲何value 不設置爲弱引用和沒有顯性的調用remove 方法也能夠回收掉了
從上面的的源碼中,咱們大概知道了引用隊列的使用,那爲何要使用引用隊列呢?假如沒有引用隊列,上面的例子咱們就須要遍歷所有的元素一個一個的去找,若是數量少那還好,若是數量多的時候,確定就會出現一些性能問題。有了引用隊列那就輕鬆能夠解決上面的問題了。從WeakReference 源碼中咱們能夠看到有兩個構造函數,第二個是須要傳入引用隊列的
public WeakReference(T referent) { super(referent); } public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); }
### 引用隊列hello word
Object referent = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); WeakReference weakReference1 = new WeakReference<>(referent); WeakReference weakReference2 = new WeakReference<>(referent, referenceQueue);
使用中須要注意的細節:
因爲弱引用的對象在GC 發生的時候均可能會被回收掉,因此在使用以前咱們都須要判斷下是否爲null 來避免空指針異常
Object referent3 = weakReference2.get(); if (referent3 != null) { // GC hasn't removed the instance yet } else { // GC has cleared the instance }
若是你以爲這篇內容對你挺有啓發,我想邀請你幫我2個小忙: