Java中的HashMap內(nèi)存泄漏問題詳解
前言
眾所周知, WeakHashMap 中的 key 是弱引用,如果再使用之后沒有及時 remove 掉這個key,那么當GC時key就可能會被回收,導致key對應的value對象占用的內(nèi)存無法回收進而導致內(nèi)存泄漏,如果有大量的key可能會導致內(nèi)存溢出、頻繁FullGC等問題。
說了這么多導致內(nèi)存泄漏是因為 WeakHashMap 的 key 是弱引用從而導致內(nèi)存泄漏,但是 HashMap 的key是強引用,也會導致內(nèi)存泄漏嗎?
Java中的四種引用
先簡單了解一下Java中的四種引用分別是什么
強引用
如果一個對象是強引用, GC 不會回收該對象(在該對象可達時),就算發(fā)生內(nèi)存溢出也不會回收該對象。一般手動 new 出來的對象都是強引用
什么時候回收強引用對象呢?平常寫代碼中那么多 new 的對象怎么沒有發(fā)生內(nèi)存溢出呢
平常代碼中有大量的例如:Objedt obj = new Object() 這種手動創(chuàng)建的對象既然都是強引用,不會被回收,JVM還不是分分鐘爆炸?
其實,強引用對象也是會被回收的,根據(jù)GC回收的算法—可達性分析算法進行判斷,當一個對象不可達時就會被回收(不可達就不繼續(xù)展開啦),或者手動將 obj=null ,也會回收該對象。因為obj指向這個 new 出來的對象,所以是可達的對象不會被回收,但是 obj 一般是在棧中,當前線程執(zhí)行完成,該方法棧就會被回收,所以obj也會被回收,進而導致堆中該對象沒有引用可達而被回收。
軟引用
如果堆內(nèi)存空間充足,則一般GC時不會回收軟引用對象,只有在堆內(nèi)存空間不足的時候才會回收軟引用內(nèi)存空間的對象。使用場景:(和弱引用相同的作用)一般可以用作本地緩存,防止緩存中的數(shù)據(jù)量太大導致內(nèi)存溢出
弱引用
無論堆內(nèi)存空間是否充足,在每次GC的時候都可能會對弱引用對象進行回收。
虛引用
虛引用,正如其名,對一個對象而言,這個引用形同虛設,有和沒有一樣,簡單來說虛引用就相當于沒有引用。它的作用就是在GC的時候會發(fā)出一個系統(tǒng)通知
HashMap內(nèi)存泄漏場景
HashMap的 put 方法分析
先分析一下HashMap的put/get
HashMap 的 put 方法在保存數(shù)據(jù)的時候會根據(jù)** key 計算出來hash值**,然后根據(jù)hash值獲取到數(shù)組的下標的位置,沒有發(fā)生hash沖突就只直接保存,如果發(fā)生hash沖突則形成鏈表。
get 方法是根據(jù)key的hash值得到數(shù)組的下標,然后直接獲取或者遍歷鏈表并調(diào)用 equals 方法來獲取數(shù)據(jù)。
第一種:內(nèi)存泄漏代碼
下面的代碼會導致內(nèi)存泄漏嗎
class Person { String name; int age; //省略getter/setter方法 ... } HashMap<Person, Integer> map = new HashMap<>(); Person gay = new Person("gay倫", 18); Person yase = new Person("亞瑟", 10); Person timo = new Person("提莫", 11); // 保存 map.put(gay, 1); map.put(yase, 2); map.put(timo, 3); // 第一次獲取 Integer x = map.get(gay); System.out.println(x); Integer y = map.get(new Person("gay倫", 18)); System.out.println(y); // 這里可以直接獲取到對象嗎?
控制臺輸出:
null
原因分析
相信注重細節(jié)的小伙伴已經(jīng)看出來了,為什么第二次沒有獲取到對應的 value 呢,這是因為 Person 沒有重寫** hashcode 和 equals ,導致默認的計算的 hashcode 不同,所以無法獲取到第一次保存的 value (還記得上面的前戲**嗎?前戲是非常重要的)
簡單說就是兩次計算的 hashcode 和 equals 不同導致HashMap的 get 方法無法獲取到對應的 value
如果 HashMap 一直沒有回收(例如定義為常量、靜態(tài)變量),那么無法 remove 對應的key,就會導致value一直被HashMap引用,從而導致占用的內(nèi)存無法回收,導致內(nèi)存泄漏。
解決方案
解決方案:如果是自定義的類,最好重寫 hashcode 和 equals ,如果是保存到Map中,則一定要重寫 hashcode 和 equals
修正代碼示例
使用IDEA自動生成的hashcode和equals
... // 加上equals和hashcode =》 IDEA自動生成 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } ...
控制臺輸出:
11
第二種:內(nèi)存泄漏代碼
看看下面的代碼是否會導致內(nèi)存泄漏
class Person { String name; int age; //省略getter/setter方法 ... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } } HashMap<Person, Integer> map = new HashMap<>(); Person gay = new Person("gay倫", 18); Person yase = new Person("亞瑟", 10); Person timo = new Person("提莫", 11); // 保存 map.put(gay, 1); map.put(yase, 2); map.put(timo, 3); // 第一次獲取 Integer x = map.get(gay); System.out.println(x); // 修改對象的name屬性 gay.setName("蓋倫"); // 第二次獲取 Integer y = map.get(gay); System.out.println(y); // 這里可以獲取到對象嗎?
控制臺輸出:
null
原因分析
怎么就改一下 name 就獲取不到了?(其實還是要看前戲)看見上面的 hashcode 和 equals 了沒有, key 的 hashcode 是通過 Objects.hash(name, age); 計算的,hash值和 name 和 age 有關的,如果其中一個改變了,那么計算出來的 hash 值就不同,計算出來的下標可能就不同,所以就獲取不到 value
解決方案
在重寫 hashcode 和 equals 方法的時候,一定要注意參與計算的相關字段,最好不要更改參與計算的字段屬性值,如果可以的話,可以將參與計算的字段定義為 final 類型
修正代碼演示
class Person { String name; // 定義為final final int age; //省略getter/setter方法 ... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; // 只判斷name相同即可 return age == person.age; } @Override public int hashCode() { // hash值通過final定義的age計算 return Objects.hash(age); } }
控制臺輸出:
11
小總結
WeakHashMap 在某些情況下因為 key 是弱引用,所以會導致內(nèi)存溢出。
ThreadLocal 為什么會內(nèi)存溢出呢?
就是因為其內(nèi)部類 Entry 繼承了 WeakReference ,所以一般使用完成之后要及時的 remove HashMap在某些情況下也會內(nèi)存溢出,所以在自定義類的時候要注意重寫equals和hashcode方法。
到此這篇關于Java中的HashMap內(nèi)存泄漏問題詳解的文章就介紹到這了,更多相關HashMap內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合Swagger Api自動生成文檔的實現(xiàn)
本文主要介紹了SpringBoot整合Swagger Api自動生成文檔的實,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06SpringBoot上傳文件到本服務器 目錄與jar包同級問題
這篇文章主要介紹了SpringBoot上傳文件到本服務器 目錄與jar包同級問題,需要的朋友可以參考下2018-11-11java 基礎知識之網(wǎng)絡通信(TCP通信、UDP通信、多播以及NIO)總結
這篇文章主要介紹了java 基礎知識之網(wǎng)絡通信總結的相關資料,包括TCP通信、UDP通信、多播以及NIO,需要的朋友可以參考下2017-03-03SpringBoot 增量部署發(fā)布的實現(xiàn)步驟
本文介紹了通過拆分項目jar包和使用類加載器實現(xiàn)Spring Boot的增量部署,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-12-12