Android內存泄漏研究

jopen 9年前發布 | 18K 次閱讀 Android Android開發 移動開發

概念

根搜索算法

Android 虛擬機的垃圾回收采用的是根搜索算法。GC 會從根節點(GC Roots)開始對 heap 進行遍歷。到最后,部分沒有直接或者間接引用到 GC Roots 的就是需要回收的垃圾,會被 GC 回收掉。

根搜索算法相比引用計數法很好的解決了循環引用的問題。舉個例子,Activity 有 View 的引用,View 也有 Activity 的引用,之前我還嘗試去源代碼里找 Activity 何時和 View 斷開連接是大錯特錯了。當 Activity finish 掉之后,Activity 和 View 的循環引用已成孤島,不再引用到 GC Roots,無需斷開也會被回收掉。

內存泄漏

Android 內存泄漏指的是進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地引用到 gc roots 導致無法被 GC 回收。無用的對象占據著內存空間,使得實際可使用內存變小,形象地說法就是內存泄漏了。

場景

  • 類的靜態變量持有大數據對象
    靜態變量長期維持到大數據對象的引用,阻止垃圾回收。
  • 非靜態內部類的靜態實例
    非靜態內部類會維持一個到外部類實例的引用,如果非靜態內部類的實例是靜態的,就會間接長期維持著外部類的引用,阻止被回收掉。
  • 資源對象未關閉
    資源性對象如 Cursor、File、Socket,應該在使用后及時關閉。未在 finally 中關閉,會導致異常情況下資源對象未被釋放的隱患。
  • 注冊對象未反注冊
    未反注冊會導致觀察者列表里維持著對象的引用,阻止垃圾回收。
  • Handler臨時性內存泄露
    Handler 通過發送 Message 與主線程交互,Message 發出之后是存儲在 MessageQueue 中的,有些 Message 也不是馬上就被處理的。在 Message 中存在一個 target,是 Handler 的一個引用,如果 Message 在 Queue 中存在的時間越長,就會導致 Handler 無法被回收。如果 Handler 是非靜態的,則會導致 Activity 或者 Service 不會被回收。
    由于 AsyncTask 內部也是 Handler 機制,同樣存在內存泄漏的風險。
    此種內存泄露,一般是臨時性的。

預防

  • 不要維持到 Activity 的長久引用,對 activity 的引用應該和 activity 本身有相同的生命周期。
  • 盡量使用context-application代替context-activity
  • Activity 中盡量不要使用非靜態內部類,可以使用靜態內部類和WeakReference代替。

檢測

靜態檢測

靜態檢測主要是檢測資源未關閉的情況,Eclipse 和 Android Studio 都可以檢測出 IO 或者 Socket 未關閉的情況,然后在 finally 中關閉即可。

動態監測

動態檢測主要是依靠 MAT 這個工具。2011 年 Google IO 有一個主題演講,非常詳細地講解了內存泄露的檢測,包含 MAT 工具的使用,值得一看。我在某項目中使用 MAT 檢測,發現一處內存泄漏,分享一下過程。從首頁到商戶列表到商戶詳情再退回首頁執行Dump HPROF File,查看 MAT 中的Histogram,過濾 Activity 后,結果如下:

Android內存泄漏研究Histogram

仍然存在ShopInfoActivity的實例,選中右鍵點擊Merge Shortest Paths to GC Roots,結果如下:

Android內存泄漏研究

此處輸入圖片的描述

可以看到ShopDatabase中維持著ShopInfoActivity的引用,查看源代碼如下:

 public class ShopDatabase {
           …
           private static ShopDatabase instance;
           public static ShopDatabase getInstance (Context context) {
               if (instance == null && context != null) {
                   instance = new ShopDatabase (context);
               }
               return instance;
           }
           protected Context context;
           …
       } 

很明顯,靜態變量instance長期持有context的引用,造成內存泄露。
所以動態檢測內存泄露的一個簡單思路就是隨意操作 APP,最后返回首頁,然后用 MAT 檢測,查看是否存在 Activity 多于一個或者 Activity 不正常存在的問題。

參考資料

來自: jiajixin.cn

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!