內存泄漏和內存溢出的區別及各自的解決辦法

最近在複習JVM相關知識的時候,多次提到了內存泄漏(OOM),心裏突然想到了內存溢出.
索性在這裏整理一下內存泄漏和內存溢出的區別及相應的解決辦法~

內存泄漏 memory leak

是指程序在申請內存後,無法釋放已申請的內存空間就造成了內存泄漏,一次內存泄漏似乎不會有大的影響,但內存泄漏堆積後的後果就是內存溢出。
舉個例子就是:我租了一個房子,只有一把鑰匙,我有事離開不住了,但是我拿的鑰匙被沒有給別人,造成的別人也進不去這個房子,這個房子就相當於內存泄漏了. 如果每個人都跟我一樣,最後的結果就是沒有房子可以出租給別人了. 如果這個時候再有人要租房子,這也就造成了內存溢出.
一次的內存泄漏並不可怕,可怕的事多次的內存泄漏造成最終的內存溢出.而且還會在這之前的過程中導致多次的GC,從而降低性能.

以發生的方式來分類,內存泄漏可以分爲4類:

  1. 常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。
  2. 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。
  3. 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由於算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。
  4. 隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對於一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,稱這類內存泄漏爲隱式內存泄漏。

內存泄漏的解決辦法:

內存泄漏的解決方法基本上還是要基於良好的編程習慣,有一個良好的編程習慣就可以避免大多數的內存泄漏.編程習慣,或者說是編程守則當然是首推阿里的Java開發手冊.阿里Java開發手冊[泰山版]

內存泄漏大部分的原因都是因爲我們在執行完需要的資源或者執行完程序之後,並沒有及時釋放、關閉資源、釋放空間,從而造成的內存的浪費.這個時候我們就需要在 執行完程序之後,及時的回收之前交給程序的資源和空間.
也有一部分的內存泄漏是由於代碼引起的.
比如:

byte[] arr1 = new byte[1024*1024*20];
byte[] arr2 = new byte[1024*1024*20];
arr2 = arr1;

這個時候,代碼執行的時候會在堆空間內開闢兩塊內存分別給arr2,和arr1,但是當arr2 = arr1 之後, 原本分配給arr2的堆空間的內存地址就會失去引用. 這個時候就是相當於造成了內存泄漏,只能等待 堆空間對應的GC觸發之後才能夠回收,如果是YGC影響還稍微小一些,但如果是直接進入老年區,等待FGC的話,造成內存泄漏的時間就會加長.
而且,如果頻繁出現這種情況,也會導致GC的高頻觸發,降低了整個程序的性能.
相關知識點可以看一下我之前的博客詳解堆空間之新生代和老年代.

還有就是在使用線程池或者連接池的情況下,在使用完對應的線程或連接後,要及時的釋放相應的線程或者連接,而且在可能的情況下,新線程儘量要創建成爲 守護線程(雖然默認就是守護線程,但是一旦用到threadfFactory創建線程的時候,一定要仔細覈對代碼,確保明確是否需要建立非守護線程).

內存溢出 out of memory

內存溢出就是你要求分配的內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。
舉個例子就是:我要買房子,我想啃老,問家裏要100W,但是家裏只能給我10W,然後因爲錢不夠導致房子沒買上(報錯體質)這就是內存溢出.

內存溢出原因:

  1. 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
  2. 集合類中有對對象的引用,使用完後未清空,產生了堆積,使得JVM不能回收;
  3. 代碼中存在死循環或循環產生過多重複的對象實體;
  4. 使用的第三方軟件中的BUG;
  5. 啓動參數內存值設定的過小

內存溢出的解決辦法

觀察Jvm日誌,如果確定是因爲一開始分配的啓動參數太小,可以通過設置 -Xms[N] -Xmx[N] 來進行設置啓動時分配給Jvm的運行時內存空間大小.
其次就是根據錯誤日誌,走讀代碼,找到對應的發生內存溢出的位置,針對錯誤做出相應的解決.
還有一點就是能夠使用棧內引用就儘量使用棧內引用,方便資源的釋放和提高程序的性能.
如下代碼:

public class StringTest {
    public StringBuilder getString1(){
        StringBuilder sb = new StringBuilder("做個測試~~");
        return sb;
    }
    public String getString2(){
        StringBuilder sb = new StringBuilder("做個測試~~");
        return sb.toString();
    }
}

getString1 返回的是一個StringBuilder 對象, 如果執行完getString1()方法的時候,對應的stringBuilder 對象並不能釋放,因爲 這個對象可能揮別其他方法調用.
但是getString2 返回的是一個String 對象,getString2()中的StringBuilder對象 在方法執行完畢後就會自動回收了,因爲他的作用範圍只有這個方法.
這樣就會減少GC發生的頻率,或者說一定概率減少發生OOM的發生概率.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章