Java中WeakHashMap的回收問題詳解
一、四大引用
在介紹WeakHashMap回收問題之前,我們要先介紹四大引用類型:
- 強(qiáng)引用是最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會回 收掉被引用的對象。
- 軟引用是用來描述一些還有用,但非必須的對象。只被軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存, 才會拋出內(nèi)存溢出異常。在JDK 1.2版之后提供了SoftReference類來實(shí)現(xiàn)軟引用。
- 弱引用也是用來描述那些非必須對象,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只 能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開始工作,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只 被弱引用關(guān)聯(lián)的對象。在JDK 1.2版之后提供了WeakReference類來實(shí)現(xiàn)弱引用。
- 虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的 存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實(shí)例。為一個對象設(shè)置虛 引用關(guān)聯(lián)的唯一目的只是為了能在這個對象被收集器回收時收到一個系統(tǒng)通知。在JDK 1.2版之后提供 了PhantomReference類來實(shí)現(xiàn)虛引用。
對于本文主要涉及的為強(qiáng)引用,弱引用。其他引用本篇就不再多提了。 總結(jié)一下強(qiáng)引用與弱引用:
- 強(qiáng)引用:主要為new出來的對象,就算內(nèi)存滿了,也不會回收它。因此當(dāng)強(qiáng)引用過多時會導(dǎo)致OOM(內(nèi)存溢出),HashMap中key、value關(guān)系就為強(qiáng)引用。
- 弱引用:被弱引用關(guān)聯(lián)的對象只 能生存到下一次垃圾收集發(fā)生為止,而WeakHashMap中的key,value就是弱引用關(guān)系。
二、WeakHashMap的回收問題
首先基本大概講述一下WeakHashMap弱鍵的原理:
大致上是通過WeakReference和ReferenceQueue實(shí)現(xiàn)的。WeakHashMap的key是“弱鍵”,即是WeakReference類型的;ReferenceQueue是一個隊列,它會保存被GC回收的“弱鍵”。
實(shí)現(xiàn)步驟是:
1、新建WeakHashMap,將“鍵值對”添加到WeakHashMap中。實(shí)際上,WeakHashMap是通過數(shù)組table保存Entry(鍵值對);每一個Entry實(shí)際上是一個單向鏈表,即Entry是鍵值對鏈表。
2、當(dāng)某“弱鍵”不再被其它對象引用,并被GC回收時。在GC回收該“弱鍵”時,這個“弱鍵”也同時會被添加到ReferenceQueue(queue)隊列中。
3、 當(dāng)下一次我們需要操作WeakHashMap時,會先同步table和queue。table中保存了全部的鍵值對,而queue中保存被GC回收的鍵值對;同步它們,就是刪除table中被GC回收的鍵值對。這就是“弱鍵”如何被自動從WeakHashMap中刪除的步驟了。和HashMap一樣,WeakHashMap是不同步的??梢允褂?Collections.synchronizedMap 方法來構(gòu)造同步的 WeakHashMap。
讀者看到這可能還是比較抽象。沒關(guān)系,接下來我們直接上一些例子:
public static void main(String[] args) {
WeakHashMap<String,String> weakMap = new WeakHashMap<>();
weakMap.put("k1","v1");
String k2 = new String("k2");
weakMap.put(k2,"v2");
weakMap.put(new String("k3"),"k3");
System.out.println("weakMap回收前打印結(jié)果:");
System.out.println(weakMap);
// 因?yàn)閃eakHashMap內(nèi)部維護(hù)的是一個弱引用映射所以gc直接回收
System.gc();
System.out.println("weakMap回收后打印結(jié)果:");
System.out.println(weakMap);
}以上的打印結(jié)果是什么呢?

可以看到打印結(jié)果是回收后的結(jié)果是只有k3的key-value被回收掉了。那么為什么呢?
這得首先從創(chuàng)建一個String對象說起,創(chuàng)建一個String對象最常見的有兩種情況:
// 第一種情況
String str1 = "a";
// 第二種情況
String str2 = new String("b");對于第一種情況其對象的創(chuàng)建會先在字符串常量池判斷有沒有對應(yīng)的字符串常量,如果沒有創(chuàng)建一個字符串常量一,然后將其引用指向在字符串常量池中的那個常量,如果存在則不需創(chuàng)建直接返回引用。 對于第二種情況其對象的引用還是會先在字符串常量池判斷有沒有對應(yīng)的字符串常量,如果沒有創(chuàng)建一個字符串常量。 而后在堆中拷貝一個其字符串常量的對象,最后返回堆上的引用。
我畫了張圖對于情況二來講應(yīng)該是這個:

那么對于對象創(chuàng)建的引用我們了解了,那么接下來回到例子就很好理解。
- 首先是 weakMap.put(“k1”,“v1”); 很明顯這里面是k1的引用是放在字符串常量池里的,是一種強(qiáng)引用。因此關(guān)于k1的key會一直保持下去。所以就是強(qiáng)引用,因此不會被gc。
然后則是:
String k2 = new String("k2");
weakMap.put(k2,"v2");這里面的很明顯k2為new String的WeakHashMap外部的強(qiáng)引用,因此k2的指針其實(shí)是在WeakHashMap外部被引用,所以不會被gc。如果要回收它也很容易,則只需要將其外部的引用k2置為null:
String k2 = new String("k2");
weakMap.put(k2,"v2");
k2 = null;而相對直接在WeakHashMap內(nèi)直接創(chuàng)建對象返回引用的話,那么很明顯就是弱鍵了。
weakMap.put(new String("k3"),"k3");那么如何可以如何才能不回收這個字符串對象呢?我們剛剛畫圖提到了,我們可以用intern()方法返回其字符串常量池對應(yīng)的對象。
weakMap.put(new String("k3").intern(),"k3");這個時候小伙伴可以自己去試一下,此時這個對象gc后也不會被回收了。就如同情況1:weakMap.put(“k1”,“v1”);
最后的最后附上完整代碼,有興趣的可以自己去試試:
public class MapGC {
public static void main(String[] args) {
WeakHashMap<String,String> weakMap = new WeakHashMap<>();
weakMap.put("k1","v1");
String k2 = new String("k2");
weakMap.put(k2,"v2");
weakMap.put(new String("k3"),"k3");
System.out.println("weakMap回收前打印結(jié)果:");
System.out.println(weakMap);
// 因?yàn)閃eakHashMap內(nèi)部維護(hù)的是一個弱引用映射所以gc直接回收
System.gc();
System.out.println("weakMap第一次回收后打印結(jié)果:");
System.out.println(weakMap);
k2 = null;
weakMap.put(new String("k3").intern(),"k3");
System.gc();
System.out.println("weakMap第二次回收后打印結(jié)果:");
System.out.println(weakMap);
}
}輸出結(jié)果:

到此這篇關(guān)于Java中WeakHashMap的回收問題詳解的文章就介紹到這了,更多相關(guān)WeakHashMap回收內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PageHelper在springboot+mybatis框架中的使用步驟及原理解析
這篇文章主要介紹了PageHelper在springboot+mybatis框架中的使用步驟及原理解析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
SpringBoot項(xiàng)目的多文件兼多線程上傳下載
本文主要介紹了SpringBoot項(xiàng)目的多文件兼多線程上傳下載,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
org.apache.ibatis.annotations不存在的問題
這篇文章主要介紹了org.apache.ibatis.annotations不存在的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
關(guān)于Integer.parseInt()方法的使用
這篇文章主要介紹了關(guān)于Integer.parseInt()方法的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11

