查詢並修復Android中的記憶體洩露—OutOfMemoryError

OneAPM官方技術部落格發表於2016-05-09

【編者按】本文作者為來自南非約翰內斯堡的女程式設計師 Rebecca Franks,Rebecca 熱衷於安卓開發,擁有4年安卓應用開發經驗。有點完美主義者,喜愛美食。

本文系國內ITOM管理平臺 OneAPM 編譯呈現,以下為正文。

Android 程式中很容易出現記憶體洩露問題。毫無戒心的開發者可能每天都會造成一些記憶體洩露,卻不自知。你可能從未注意過這類錯誤,或者甚至都不知道它們的存在。直到你遇到下面這樣的異常:

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 錯誤,十有八九是因為記憶體洩露。當筆者第一次遇到這種堆疊跟蹤時,也感到迷惑不解,想著是不是點陣圖太大了……實際上,我那會兒真是大錯特錯。

什麼是記憶體洩露?

記憶體洩露是指程式釋放廢棄記憶體失敗,導致效能受損或出現中斷。

Android 程式中的記憶體洩露是如何產生的?

Android 程式中的記憶體洩露很容易產生,這也是問題的一部分。然而,最大的問題在於 Android Context(上下文) 物件。

每個 app 都有一個全域性的應用上下文物件( getApplicationContext())。每個 Activity (活動)都是 Context 的子類,儲存著與當前活動相關的資訊。通常,記憶體洩露都與已洩露的活動(leaked activtiy)相關。

通常,一般的開發者會把上下文物件(context object)傳給需要的執行緒。建立一些靜態的 TextViews 以儲存指向活動的引用。但是,你懂的,這樣可行嗎?

在此情況下,如果使用記憶體監視器就會發現,app 的記憶體使用率不斷增加,正如下面的 Android 記憶體監控器所示:

查詢並修復Android中的記憶體洩露—OutOfMemoryError 存在記憶體洩露問題的 app 在執行時,Android 記憶體監控器的情況

查詢並修復Android中的記憶體洩露—OutOfMemoryError 解決記憶體洩露問題後,Android 記憶體監控器的情況

如你所見,在第一張圖中,app 永遠都無法回收一部分已經使用的記憶體。在 OutOfMemoryError 錯誤出現之前,它一度使用了300MB的記憶體。而第二張圖則顯示,app 能順利進行垃圾回收,重得一部分記憶體,從而保持相當穩定的記憶體使用量。

如何避免記憶體洩露?

  • 避免在 activity 或 fragment 之外傳遞 Context 物件。
  • 永遠永遠不要建立靜態的 Context 或 View 物件,或者將二者儲存於靜態變數中。這是記憶體洩露的首要標誌。

    private static TextView textView; //DO NOT DO THIS private static Context context; //DO NOT DO THIS

  • 總是記得在 onPause() 或 onDestroy() 方法中的取消註冊監聽器(listeners)。這包括 Android 監聽器,以及位置服務、顯示管理器服務,還有自定義的一些監聽器。

  • 不要在 AsyncTasks(非同步任務)或後臺執行緒中儲存指向 activities 的強引用。Activity 可能會關閉,但是 AsyncTask 會繼續執行,一直儲存著對該 activity 的引用。

  • 如果可以,使用 Context-application (getApplicationContext()),而不是某個 activity 的 Context 物件。

  • 盡力避免使用非靜態的內部類。將引用儲存至某個 Activity 或 View 內部會導致記憶體洩露。如果不得不儲存引用,請使用 WeakReference

如何修復記憶體洩露問題?

修復記憶體洩露問題需要許多實踐,不斷嘗試、試錯,才能取得成功。通常,記憶體洩露並不容易定位。值得慶幸的是,有許多現成的工具可以幫你找出潛在的洩露問題。

1、開啟 Android Studio,開啟 Android Monitor(監控器)選項。 2、執行你的應用,從可選應用中進行選擇你的應用,並執行之。 3、在 app 中進行一些操作,以達到類似的效果。譬如筆者,開啟了一個新視訊,播放了50次。(為此,筆者寫了一個測試程式。) 4、此處的關鍵,是在出現 OutOfMemoryException 異常之前捕獲應用的問題。 5、點選 Android 監控器中的記憶體選項。 <img src="http://blog.oneapm.com/tags-Java.html" alt="查詢並修復Android中的記憶體洩露—OutOfMemoryError](http://i2.wp.com/riggaroo.co.za/wp-content/uploads/2016/01/Screen-Shot-2016-01-26-at-11.12.00-PM.png) 6、你會看到一張動態繪製的圖表。準備好之後,點選“啟動垃圾回收(Initiate GC)“(紅色的垃圾卡車圖示)。 7、點選“傾倒 [Java" /> 堆記憶體(Dump Java Heap)”,之後等待數秒。(卡車圖示下面帶有綠色箭頭的圖示)。這會生成一個 .hprof 檔案,你可以用來分析記憶體使用率。 8、不幸的是,Android Studio Hprof 檔案檢視器不具備 Eclipse 記憶體分析器的所有小工具。因此,你需要安裝 MAT。 9、執行下面的指令,將 Android 的 .hprof 檔案轉換為 MAT 能夠理解的格式。(hprof-conv 工具位於 sdk 的平臺工具資料夾下)

./hprof-conv path/file.hprof exitPath/heap-converted.hprof

10、轉換完成後,在 MAT 中開啟該檔案。選擇“洩露疑點報告(Leak Suspects Report)”,之後點選完成。 查詢並修復Android中的記憶體洩露—OutOfMemoryError 開啟 Eclipse 記憶體分析器 —— 選擇洩露疑點報告 11、點選頂部的三個藍色柱形圖示,“為任意物件集合建立一個直方圖”。你會看到佔用記憶體的一列物件。 查詢並修復Android中的記憶體洩露—OutOfMemoryError Eclipse 記憶體分析器 — 直方圖 12、檢視這麼多物件或許會讓人摸不到頭腦。其實,你可以根據類名進行過濾,因此筆者建議你在類名過濾器中輸入類名。 查詢並修復Android中的記憶體洩露—OutOfMemoryError 技術分享 第6張根據類名在 Eclipse 記憶體分析器中過濾物件

13、現在,我們看到 VideoDetailActivity 存在9個例項。這顯然是不對的,因為我們其實只需要一個。進一步檢視誰儲存著 VideoDetailActivity 的引用,右鍵點選該專案,選擇“合併垃圾回收根的最短路徑(Merge Paths to Shortest GC Root)”,然後點選“排除所有虛/弱/軟引用(exclude all phantom/weak/soft etc. references)。”
查詢並修復Android中的記憶體洩露—OutOfMemoryError Eclipse 記憶體分析器——合併垃圾回收根的最短路徑

現在,儲存著引用的執行緒就會顯示出來。之後,你可以追根溯源,找到儲存該 activity 引用的具體例項。

14、根據下面的資訊,顯然,有一個 DisplayListener 物件在登記之後從未登出過。 查詢並修復Android中的記憶體洩露—OutOfMemoryError Eclipse 記憶體分析器 — 記憶體洩露識別

因此,對這個此前登記的顯示監聽器(display listener)呼叫登出方法,就能解決此記憶體洩露問題。

DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);displayManager.unregisterDisplayListener(listener);

不過,並非所有的記憶體洩露都這麼容易找到,也有一些非常難找。但是,希望本文能使你開始尋找問題根源,並避免潛在的記憶體洩露問題。此外,還有許多有助於尋找記憶體洩露問題的工具,點選此處進行檢視。

參考連結:

OneAPM Mobile Insight真實使用者體驗為度量標準進行 Crash 分析,監控網路請求及網路錯誤,提升使用者留存。訪問 OneAPM 官方網站感受更多應用效能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格

本文轉自 OneAPM 官方部落格

原文地址:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/

相關文章