1. 程式人生 > >記憶體洩漏和記憶體溢位的區別與解決方式

記憶體洩漏和記憶體溢位的區別與解決方式

記憶體洩漏(memory leak ) 記憶體溢位 (out of memory)

記憶體洩露 :是指程式在申請記憶體後,無法釋放已申請的記憶體空間就造成了記憶體洩漏,一次記憶體洩漏似乎不會有大的影響,但記憶體洩漏堆積後的後果就是記憶體溢位。

我們知道了記憶體洩漏的原因而記憶體溢位則有可能是因為我們我們多次記憶體洩漏堆積後的後果則變成了記憶體溢位

記憶體溢位: 指程式申請記憶體時,沒有足夠的記憶體供申請者使用,或者說,給了你一塊儲存int型別資料的儲存空間,但是你卻儲存long型別的資料,那麼結果就是記憶體不夠用,此時就會報錯OOM,即所謂的記憶體溢位,簡單來說就是自己所需要使用的空間比我們擁有的記憶體大記憶體不夠使用所造成的記憶體溢位。

記憶體洩漏的分類(按發生方式來分類)

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

記憶體洩漏的解決方法:

  1. 記憶體洩漏也許是因為活動已經被使用完畢,但是仍然在其他地方被引用,導致無法對其進行回收。我們只需要給對活動進行引用的類獨立出來或者將其變為靜態類,該類隨著活動的結束而結束,也就沒有了當活動結束但仍然還被其他類引用的情況。
  2. 資源性物件在不使用的時候,應該呼叫它的close()函式將其關閉掉。。
  3. 集合容器中的記憶體洩露 ,我們通常把一些物件的引用加入到了集合容器(比如ArrayList)中,當我們不需要該物件時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
    需要在退出程式之前,將集合裡的東西clear,然後置為null,再退出程式。

  4. WebView造成的洩露,當我們不使用WebView物件時,應該呼叫它的destory()函式來銷燬它,並釋放其佔用的記憶體,否則其長期佔用的記憶體也不能被回收,從而造成記憶體洩露。
    我們應該為WebView另外開啟一個程序,通過AIDL與主執行緒進行通訊,WebView所在的程序可以根據業務的需要選擇合適的時機進行銷燬,從而達到記憶體的完整釋放。

記憶體溢位的原因及解決方法:

  1. 記憶體溢位原因:
    1.記憶體中載入的資料量過於龐大,如一次從資料庫取出過多資料;
    2.集合類中有對物件的引用,使用完後未清空,產生了堆積,使得JVM不能回收;
    3.程式碼中存在死迴圈或迴圈產生過多重複的物件實體;
    4.使用的第三方軟體中的BUG;
    5.啟動引數記憶體值設定的過小
  2. 記憶體溢位的解決方案:
    第一步,修改JVM啟動引數,直接增加記憶體。(-Xms,-Xmx引數一定不要忘記加。)

    第二步,檢查錯誤日誌,檢視“OutOfMemory”錯誤前是否有其 它異常或錯誤。

    第三步,對程式碼進行走查和分析,找出可能發生記憶體溢位的位置。

重點排查以下幾點:
1.檢查對資料庫查詢中,是否有一次獲得全部資料的查詢。一般來說,如果一次取十萬條記錄到記憶體,就可能引起記憶體溢位。這個問題比較隱蔽,在上線前,資料庫中資料較少,不容易出問題,上線後,資料庫中資料多了,一次查詢就有可能引起記憶體溢位。因此對於資料庫查詢儘量採用分頁的方式查詢。

2.檢查程式碼中是否有死迴圈或遞迴呼叫。

3.檢查是否有大迴圈重複產生新物件實體。

4.檢查List、MAP等集合物件是否有使用完後,未清除的問題。List、MAP等集合物件會始終存有對物件的引用,使得這些物件不能被GC回收。

第四步,使用記憶體檢視工具動態檢視記憶體使用情況

 

如何避免記憶體洩漏?

1、在涉及使用Context時,對於生命週期比Activity長的物件應該使用Application的Context。凡是使用Context優先考慮Application的Context,當然它並不是萬能的,對於有些地方則必須使用Activity的Context。對於Application,Service,Activity三者的Context的應用場景如下:

 

 

其中,NO1表示Application和Service可以啟動一個Activity,不過需要建立一個新的task任務佇列。而對於Dialog而言,只有在Activity中才能建立。除此之外三者都可以使用。

2、對於需要在靜態內部類中使用非靜態外部成員變數(如:Context、View ),可以在靜態內部類中使用弱引用來引用外部類的變數來避免記憶體洩漏。
3、對於不再需要使用的物件,顯示的將其賦值為null,比如使用完Bitmap後先呼叫recycle(),再賦為null。
4、保持對物件生命週期的敏感,特別注意單例、靜態物件、全域性性集合等的生命週期。
5、對於生命週期比Activity長的內部類物件,並且內部類中使用了外部類的成員變數,可以這樣做避免記憶體洩漏:
1將內部類改為靜態內部類
2靜態內部類中使用弱引用來引用外部類的成員變數

如何檢查和分析記憶體洩漏?

因為記憶體洩漏是在堆記憶體中,所以對我們來說並不是可見的。通常我們可以藉助MAT、LeakCanary等工具來檢測應用程式是否存在記憶體洩漏。
1、MAT是一款強大的記憶體分析工具,功能繁多而複雜。
2、LeakCanary則是由Square開源的一款輕量級的第三方記憶體洩漏檢測工具,當檢測到程式中產生記憶體洩漏時,它將以最直觀的方式告訴我們哪裡產生了記憶體洩漏和導致誰洩漏了而不能被回收。

 

 

 

程式有始有終,我們負責創造它,也需要負責將它銷燬。