Java中WeakHashMap的回收問題詳解
一、四大引用
在介紹WeakHashMap回收問題之前,我們要先介紹四大引用類型:
- 強(qiáng)引用是最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“Object obj=new Object()”這種引用關(guān)系。無(wú)論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回 收掉被引用的對(duì)象。
- 軟引用是用來(lái)描述一些還有用,但非必須的對(duì)象。只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存, 才會(huì)拋出內(nèi)存溢出異常。在JDK 1.2版之后提供了SoftReference類來(lái)實(shí)現(xiàn)軟引用。
- 弱引用也是用來(lái)描述那些非必須對(duì)象,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只 能生存到下一次垃圾收集發(fā)生為止。當(dāng)垃圾收集器開始工作,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只 被弱引用關(guān)聯(lián)的對(duì)象。在JDK 1.2版之后提供了WeakReference類來(lái)實(shí)現(xiàn)弱引用。
- 虛引用也稱為“幽靈引用”或者“幻影引用”,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象是否有虛引用的 存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛 引用關(guān)聯(lián)的唯一目的只是為了能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。在JDK 1.2版之后提供 了PhantomReference類來(lái)實(shí)現(xiàn)虛引用。
對(duì)于本文主要涉及的為強(qiáng)引用,弱引用。其他引用本篇就不再多提了。 總結(jié)一下強(qiáng)引用與弱引用:
- 強(qiáng)引用:主要為new出來(lái)的對(duì)象,就算內(nèi)存滿了,也不會(huì)回收它。因此當(dāng)強(qiáng)引用過多時(shí)會(huì)導(dǎo)致OOM(內(nèi)存溢出),HashMap中key、value關(guān)系就為強(qiáng)引用。
- 弱引用:被弱引用關(guān)聯(lián)的對(duì)象只 能生存到下一次垃圾收集發(fā)生為止,而WeakHashMap中的key,value就是弱引用關(guān)系。
二、WeakHashMap的回收問題
首先基本大概講述一下WeakHashMap弱鍵的原理:
大致上是通過WeakReference和ReferenceQueue實(shí)現(xiàn)的。WeakHashMap的key是“弱鍵”,即是WeakReference類型的;ReferenceQueue是一個(gè)隊(duì)列,它會(huì)保存被GC回收的“弱鍵”。
實(shí)現(xiàn)步驟是:
1、新建WeakHashMap,將“鍵值對(duì)”添加到WeakHashMap中。實(shí)際上,WeakHashMap是通過數(shù)組table保存Entry(鍵值對(duì));每一個(gè)Entry實(shí)際上是一個(gè)單向鏈表,即Entry是鍵值對(duì)鏈表。
2、當(dāng)某“弱鍵”不再被其它對(duì)象引用,并被GC回收時(shí)。在GC回收該“弱鍵”時(shí),這個(gè)“弱鍵”也同時(shí)會(huì)被添加到ReferenceQueue(queue)隊(duì)列中。
3、 當(dāng)下一次我們需要操作WeakHashMap時(shí),會(huì)先同步table和queue。table中保存了全部的鍵值對(duì),而queue中保存被GC回收的鍵值對(duì);同步它們,就是刪除table中被GC回收的鍵值對(duì)。這就是“弱鍵”如何被自動(dòng)從WeakHashMap中刪除的步驟了。和HashMap一樣,WeakHashMap是不同步的??梢允褂?Collections.synchronizedMap 方法來(lái)構(gòu)造同步的 WeakHashMap。
讀者看到這可能還是比較抽象。沒關(guān)系,接下來(lái)我們直接上一些例子:
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ù)的是一個(gè)弱引用映射所以gc直接回收 System.gc(); System.out.println("weakMap回收后打印結(jié)果:"); System.out.println(weakMap); }
以上的打印結(jié)果是什么呢?
可以看到打印結(jié)果是回收后的結(jié)果是只有k3的key-value被回收掉了。那么為什么呢?
這得首先從創(chuàng)建一個(gè)String對(duì)象說(shuō)起,創(chuàng)建一個(gè)String對(duì)象最常見的有兩種情況:
// 第一種情況 String str1 = "a"; // 第二種情況 String str2 = new String("b");
對(duì)于第一種情況其對(duì)象的創(chuàng)建會(huì)先在字符串常量池判斷有沒有對(duì)應(yīng)的字符串常量,如果沒有創(chuàng)建一個(gè)字符串常量一,然后將其引用指向在字符串常量池中的那個(gè)常量,如果存在則不需創(chuàng)建直接返回引用。 對(duì)于第二種情況其對(duì)象的引用還是會(huì)先在字符串常量池判斷有沒有對(duì)應(yīng)的字符串常量,如果沒有創(chuàng)建一個(gè)字符串常量。 而后在堆中拷貝一個(gè)其字符串常量的對(duì)象,最后返回堆上的引用。
我畫了張圖對(duì)于情況二來(lái)講應(yīng)該是這個(gè):
那么對(duì)于對(duì)象創(chuàng)建的引用我們了解了,那么接下來(lái)回到例子就很好理解。
- 首先是 weakMap.put(“k1”,“v1”); 很明顯這里面是k1的引用是放在字符串常量池里的,是一種強(qiáng)引用。因此關(guān)于k1的key會(huì)一直保持下去。所以就是強(qiáng)引用,因此不會(huì)被gc。
然后則是:
String k2 = new String("k2"); weakMap.put(k2,"v2");
這里面的很明顯k2為new String的WeakHashMap外部的強(qiáng)引用,因此k2的指針其實(shí)是在WeakHashMap外部被引用,所以不會(huì)被gc。如果要回收它也很容易,則只需要將其外部的引用k2置為null:
String k2 = new String("k2"); weakMap.put(k2,"v2"); k2 = null;
而相對(duì)直接在WeakHashMap內(nèi)直接創(chuàng)建對(duì)象返回引用的話,那么很明顯就是弱鍵了。
weakMap.put(new String("k3"),"k3");
那么如何可以如何才能不回收這個(gè)字符串對(duì)象呢?我們剛剛畫圖提到了,我們可以用intern()方法返回其字符串常量池對(duì)應(yīng)的對(duì)象。
weakMap.put(new String("k3").intern(),"k3");
這個(gè)時(shí)候小伙伴可以自己去試一下,此時(shí)這個(gè)對(duì)象gc后也不會(huì)被回收了。就如同情況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ù)的是一個(gè)弱引用映射所以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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多線程實(shí)現(xiàn)第三方數(shù)據(jù)同步
這篇文章主要為大家詳細(xì)介紹了Java多線程實(shí)現(xiàn)第三方數(shù)據(jù)同步,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08淺析IDEA如何正確配置Gradle? GRADLE_USER_HOME 和 Gradle user home的區(qū)別
這篇文章主要介紹了IDEA如何正確配置Gradle? GRADLE_USER_HOME 和 Gradle user home的區(qū)別,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Java編程redisson實(shí)現(xiàn)分布式鎖代碼示例
這篇文章主要介紹了Java編程redisson實(shí)現(xiàn)分布式鎖代碼示例,小編覺得還是比較不錯(cuò)的,這里給大家分享下,供需要的朋友參考。2017-10-10Java基于虹軟實(shí)現(xiàn)人臉識(shí)別、人臉比對(duì)、活性檢測(cè)等
本文主要介紹了Java基于虹軟實(shí)現(xiàn)人臉識(shí)別、人臉比對(duì)、活性檢測(cè)等,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02MybatisPlus+Postgresql整合的幾個(gè)坑及解決
這篇文章主要介紹了MybatisPlus+Postgresql整合的幾個(gè)坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作
這篇文章主要介紹了Flink實(shí)現(xiàn)特定統(tǒng)計(jì)的歸約聚合reduce操作,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02