經驗之談:內存泄露的原因以及分析


經驗之談:內存泄露的原因以及分析

內存泄露是Javaer聽到最多的關於內存的事了,這篇文章就來談談這件事。

內存泄露與資源泄露

什么是泄露?泄露在計算機語境下,通常指的是某個資源無法被訪問,也無法被釋放。

內存泄露一般發生在某個對象的引用丟失,無法再訪問到該引用,但是該引用卻依舊引用着某個對象,導致這個對象無法回收,最終導致內存溢出OOM。

資源泄露一般發生在連接池,IO流等場景,如從連接池中每次都新建連接但不關閉,每次都打開新的IO流但不關閉,等等情況。

內存泄露發生的情況

內存泄露多發生於static的集合中,比如當你定義了一個static HashMap,此時將某個key-value放入其中后,方法段結束。

這時,除非調用map的clear方法,否則顯然該value將無限持有對象的引用,無法釋放。

public static Map<String,Object> objectMap=new HashMap<>();
public static void main(String[] args) {
    Integer a=new Integer(1);
    objectMap.put("testKey",a);
}

這種寫法看似可笑,卻很難避免,尤其在大量框架代碼中,反而更容易發生,因為大部分框架代碼對業務代碼的增強,都是通過AOP方式來做的,此時對業務代碼來說,這類隱式的static Map難以防范。

內存使用過高,一定是內存泄露嗎?

內存使用過高,並不一定是內存泄露導致的結果,具體要看內存堆的分析。

一般內存泄露最直觀的體現就是:

  1. 內存使用高
  2. GC回收不了內存,即GC前后堆大小幾乎無變化
  3. JVM瘋狂GC,CPU打滿
  4. Java進程觸發Linux操作系統的OOM-killer,Java進程被殺死
  5. 或者CPU被GC任務打滿,服務器實際宕機。

但是這不一定是泄露導致的,也有可能是內存的錯誤使用導致的,不過大同小異,主要還是需要排查異常內存的使用。

Ps:之所以作者這么說,是因為作者曾經在線上遇到了架構組修改日志框架,錯誤的將日志內容作為了key存入了map,本應的key-value應該為traceId-日志內容,結果架構組卻將key-value搞反了,導致大量的巨大key打滿了內存,堆dump文件里全是幾十k幾十k的字符串。

如何避免內存泄露

根據上面說的內存泄露多數發生的情況,避免內存泄露的策略也就十分簡單了。

  1. 盡量使用局部變量
  2. 減少使用static集合
  3. 如果必要的使用static集合,盡量使用弱引用等低級引用。比如參照ThreadLocal中的設計:TheadLocal原理

內存泄露問題如何排查

內存泄露或內存持續使用較高時,通常通過堆的情況來排查。

首先可以通過jmap -histo:live pid|less 命令,查看堆內對象使用情況。此時如果內存泄露,一般都是會某個基本類型對象過多,然后可以與正常的服務作對比,看哪個對象的數量異常的多,此時如果可以判斷出來,也沒必要dump了。

如果通過jmap無法斷定,則可以使用jmap -dump:live,format=b,file= 命令,生成dump文件。

將dump文件通過java原生的軟件或者eclipse的mat工具,就可以看到哪些對象占用過多,此時你應該關注的是非基本類型對象的其他對象,因為一般來說都是基本類型的數量和大小最多。

一般來說,你會看到以下現象:

  1. 某個map的Node十分多,有幾十萬個。
  2. 某個框架的某個對象十分多。
  3. char數據,也就是C[],占用十分多,因為有很多大字符串。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM