Android 內存泄漏工具使用

首先總結一下,平時編碼過程需要注意的事項,避免OOM
我之前也有一篇文章介紹過:
Android內存溢出 內存泄漏
其它很詳細的介紹文章也可以參考:
1. Android內存優化之OOM
2. Android應用開發性能優化完全分析

  1. 時刻記得不要加載過大的Bitmap對象;譬如對於類似圖片加載我們要通過BitmapFactory.Options設置圖片的一些採樣比率和複用等,具體做法點我參考官方文檔,不過過我們一般都用fresco或Glide開源庫進行加載。
  2. 優化界面交互過程中頻繁的內存使用;譬如在列表等操作中只加載可見區域的Bitmap、滑動時不加載、停止滑動後再開始加載。
  3. 有些地方避免使用強引用,替換爲弱引用等操作。
  4. 對批量加載等操作進行緩存設計,譬如列表圖片顯示,Adapter的convertView緩存等。
  5. 儘可能的複用資源;譬如系統本身有很多字符串、顏色、圖片、動畫、樣式以及簡單佈局等資源可供我們直接使用,我們自己也要儘量複用style等資源達到節約內存。
  6. 對於有緩存等存在的應用盡量實現onLowMemory()和onTrimMemory()方法。
  7. 儘量使用線程池替代多線程操作,這樣可以節約內存及CPU佔用率。
  8. 儘量管理好自己的Service、Thread等後臺的生命週期,不要浪費內存佔用。
  9. 儘可能的不要使用依賴注入,中看不中用。
  10. 儘量在做一些大內存分配等可疑內存操作時進行try catch操作,避免不必要的應用閃退。
  11. 儘量的優化自己的代碼,減少冗餘,進行編譯打包等優化對齊處理,避免類加載時浪費內存。
  12. 關閉資源對象,比如數據庫操作中得Cursor,IO操作的對象
  13. 調用了registerReceiver註冊廣播後,調用unregisterReceiver()來取消
  14. 調用了View.getViewTreeObserver().addOnXXXListener, 調用View.getViewTreeObserver().removeXXXListener
  15. 隨時注意Context的使用是否會導致內存泄漏

現在開始看看,怎麼來分析內存泄漏

LeakCanary

使用

參考《LeakCanary 中文使用說明》

在 build.gradle 中加入引用,不同的編譯使用不同的引用:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
 }

在 Application 中:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

這樣,就萬事俱備了! 在 debug build 中,如果檢測到某個 activity 有內存泄露,LeakCanary 就是自動地顯示一個通知。

工作機制

  1. RefWatcher.watch() 創建一個 KeyedWeakReference 到要被監控的對象。
  2. 然後在後臺線程檢查引用是否被清除,如果沒有,調用GC。
  3. 如果引用還是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。
  4. 在另外一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件。
  5. 得益於唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄露。
  6. HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並確定是否是泄露。如果是的話,建立導致泄露的引用鏈。
  7. 引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展示出來。

demo

一個非常簡單的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo 開始使用

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        TextView textView = (TextView) findViewById(R.id.test_text_view);

        TestDataModel.getInstance().setRetainedTextView(textView);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
public class TestDataModel {

    private static TestDataModel sInstance;
    private TextView mRetainedTextView;

    public static TestDataModel getInstance() {
        if (sInstance == null) {
            sInstance = new TestDataModel();
        }
        return sInstance;
    }

    public void setRetainedTextView(TextView textView) {
        mRetainedTextView = textView;
    }
}

很明顯,sInstance是靜態的,然後持有textView,textview當然持有當前context,就是TestActivity了,所以內存泄漏
退出TestActivity之後,然後過個10s左右,然後出現一個通知

此圖說明,sInstance引用了mRetainedTextView,mRetainedTextView引用了mContext,其實mContext就是TestActivity,所以最後泄漏了TestActivity這個instance,導致GC的時候不能被回收。

Eclipse MAT

參考:
1. 內存分析工具 MAT 的使用
2. Android 性能優化之使用MAT分析內存泄露問題

注意生成hprof文件之前,需要先手動的GC,然後再分析

GC_CONCURRENT 當你的堆內存快被用完的時候,就會觸發這個GC回收
GC_FOR_MALLOC 堆內存已經滿了,同時又要試圖分配新的內存,所以系統要回收內存
GC_EXTERNAL_ALLOC 在Android3.0 (Honeycomb)以前,釋放通過外部內存(比如在2.3以前,產生的Bitmap對象存儲在Native Memory中)時產生。Android3.0和更高版本中不再有這種類型的內存分配了。
GC_EXPLICIT 調用System.gc時產生,上圖中就是點擊Cause GC按鈕手動觸發垃圾回收器產生的log信息

MAT中
Merge Shortest Paths to GC Roots 可以查看一個對象到RC Roots是否存在引用鏈相連接, 在JAVA中是通過可達性(Reachability Analysis)來判斷對象是否存活,這個算法的基本思想是通過一系列的稱謂”GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走得路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連則該對象被判定爲可以被回收的對象,反之不能被回收,我們可以選擇 exclude all phantom/weak/soft etc.references(排查虛引用/弱引用/軟引用等)因爲被虛引用/弱引用/軟引用的對象可以直接被GC給回收.

AS中生成hprof文件


然後選擇文件,點擊右鍵轉換成標準的hprof文件,就可以在MAT中打開了。
在使用Eclipse或者AndroidStudio抓內存之前,一定要手動點擊 Initiate GC按鈕手動觸發GC


第二個按鈕就是GC按鈕,第三個就是生成AS可查看的hprof文件按鈕

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