1. 程式人生 > >在Android中解決記憶體溢位 – OutOfMemoryError

在Android中解決記憶體溢位 – OutOfMemoryError

注:本文在原文基礎上在如何判斷記憶體是否洩露方面進行了補充

安卓開發中經常出現記憶體溢位的情況,沒有防備的開發者可能一天會不經意間寫好幾個記憶體溢位的漏洞。你可能不會發現這些漏洞,甚至都不知道它們存在,直到你看到這種異常:

java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988)
at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580)
at android.content.res.Resources.loadDrawable(Resources.java:2487)
at android.content.res.Resources.getDrawable(Resources.java:814)
at android.content.res.Resources.getDrawable(Resources.java:767)
at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)

這是啥意思呢?難道我的Bitmap太大了?

這種異常資訊可能會讓你產生誤解。當你看到OutOfMemoryError的時候,十有八九是記憶體洩露。我第一次看到這個就以為我的Bitmap太大了,唉當時的我太年輕了。

什麼是記憶體洩露?

記憶體洩露指程式中沒有正確的釋放用完的記憶體空間,導致執行異常或錯誤

Android中記憶體洩露是如何發生的?

事實上Android中記憶體洩露很容易發生。最大的問題就是Context物件。

每一個App都有一個全域性的Application Context物件,可以通過getApplicationContext()方法獲得。每一個Activity都是Context的子類,會儲存有關當前Activity的資訊。很多情況下,記憶體洩露與Activity洩露有關。比如有的開發者會將Context物件在各個執行緒裡傳來傳去,寫一些靜態的TextView,讓它們持有Activity的引用,這就是一個典型的錯誤。

判斷是否有記憶體洩露

Memory Monitor

  1. 將裝置或模擬器與Android Studio連線。

  2. 在Android Studio中執行程式。

  3. 在螢幕下方點選Android標籤,再點選Memory標籤,選擇裝置與要監控的應用。一旦Memory Monitor開始監控裝置,就會出現藍色的圖表顯示記憶體使用情況。深藍色部分為當前應用正在使用的記憶體,淺藍色(深藍色上面那一塊)為可以使用、未被分配的記憶體空間。

    演示圖片

  4. 點選垃圾車圖示(上圖右側那個小圖示)可以觸發GC(記憶體回收)。

有記憶體溢位漏洞的應用在不斷操作並觸發GC後的圖表不斷上升的:

有記憶體洩露

記憶體洩露被解決後的應用不斷操作並不斷觸發GC後圖表波動但維持穩定:

無記憶體洩露

DDMS

開啟DDMS介面,在左側面板中選擇要觀察的程序,點選左上角的Update Heap按鈕(紅圈圈出的圓柱體按鈕),再點選右側面板中的Heap標籤,然後不斷操作並持續點選Cause GC按鈕,觀察表中data object的Total Size的數值變化。如果持續增高而不下降,那就很可能是有記憶體洩露發生了。

DDMS

有記憶體洩露時,應用不會重新得到用過的記憶體空間,當用到300MB的時候,OutOfMemoryError就發生了。從修復了記憶體洩露的應用的圖表可以看出,應用可以回收一些記憶體,並處在長期穩定狀態。

如何避免記憶體洩露?

  • 不要將Context物件傳給activity與fragment以外的物件。

  • 永遠不要將Context和View儲存在靜態變數中。

      private static TextView textView;//不要這麼幹
      private static Context context;//不要這麼幹
    
  • 在onPause()/onDestroy()方法中解除監聽器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己寫的Listener。

  • 不要在後臺執行緒與AsyncTask中儲存activity的強引用。不然當Activity被關閉後,由於AsyncTask仍在執行且持有Activity的強引用,導致Activity無法被回收。

  • 使用Application Context而不是Activity的Context

  • 儘量不要用非靜態內部類,因為它會持有外部類的引用。在非靜態內部類中儲存Activity或View的引用會導致記憶體洩露。如需儲存就使用WeakReference。

如何解決記憶體洩露?

解決記憶體洩露需要大量的錯誤積累與練習。記憶體洩露可能會很難追蹤,但藉助一些工具可以幫助你識別可能的漏洞。這裡我們使用MAT進行記憶體洩露的檢測。

  1. 開啟Android Studio,點選下方的Android Monitor標籤。

  2. 執行應用,並選擇應用來監控。

  3. 反覆進行某個可能存在記憶體洩露的操作,比如返回開啟關閉某個Activity。

  4. 在丟擲OutOfMemoryException之前點選Android Monitor中的Memory標籤。此時圖表開始繪製,過一會點選GC圖示。(剛才說的那個紅色垃圾車圖示)

    hprof1

  5. 點選垃圾車下面那個有綠色箭頭的圖示”Dump Java Heap”,並等待幾秒。然後會生成一個.hprof檔案,這個檔案就是用來分析記憶體使用的。

  6. 下載MAT

  7. 使用sdk中platform-tools路徑下的hprof-conv檔案將剛才生成的.hprof檔案轉碼成MAT可以解析的檔案。

     ./hprof-conv path/file.hprof exitPath/heap-converted.hprof
    
  8. 使用MAT開啟轉碼後的檔案。選擇”Leak Suspects Report”然後點選”Finish”。

    hprof2

  9. 點選介面上方三條豎槓的圖示。而後你會看到佔用記憶體的物件的列表。

    hprof3

  10. 這個列表可能難以理解,你可以輸入類名來過濾結果。我建議在輸入框中輸入package name,如下:

    hprof4

  11. 現在我們可以看到VideoDetailActivity的9個例項,這顯然不對,因為我們只應該有一個。我們可以右鍵單擊這一條目並選擇”Merge Paths to Shortest GC Root”並點選“exclude all phantom/weak/soft etc.references”來看是什麼持有VideoDetailActivity的引用。

    hprof5

    而後持有引用的的執行緒就會顯示出來。而後你就可以深入觀察什麼持有了Activity的引用。

  12. 通過下面的資訊我們可以看到有一個DisplayListener被註冊了但從未解除。

    hprof6

所以這個漏洞就找到了,只需要解除監聽器就可以了。

不是所有的記憶體洩露漏洞都這麼容易找到。希望這篇文章可以幫你避免記憶體洩露並在記憶體洩露發生時找到漏洞。

參考文獻:

轉載自:http://blog.chengdazhi.com/index.php/89