首先總結一下,平時編碼過程需要注意的事項,避免OOM
我之前也有一篇文章介紹過:
Android內存溢出 內存泄漏
其它很詳細的介紹文章也可以參考:
1. Android內存優化之OOM
2. Android應用開發性能優化完全分析
- 時刻記得不要加載過大的Bitmap對象;譬如對於類似圖片加載我們要通過BitmapFactory.Options設置圖片的一些採樣比率和複用等,具體做法點我參考官方文檔,不過過我們一般都用fresco或Glide開源庫進行加載。
- 優化界面交互過程中頻繁的內存使用;譬如在列表等操作中只加載可見區域的Bitmap、滑動時不加載、停止滑動後再開始加載。
- 有些地方避免使用強引用,替換爲弱引用等操作。
- 對批量加載等操作進行緩存設計,譬如列表圖片顯示,Adapter的convertView緩存等。
- 儘可能的複用資源;譬如系統本身有很多字符串、顏色、圖片、動畫、樣式以及簡單佈局等資源可供我們直接使用,我們自己也要儘量複用style等資源達到節約內存。
- 對於有緩存等存在的應用盡量實現onLowMemory()和onTrimMemory()方法。
- 儘量使用線程池替代多線程操作,這樣可以節約內存及CPU佔用率。
- 儘量管理好自己的Service、Thread等後臺的生命週期,不要浪費內存佔用。
- 儘可能的不要使用依賴注入,中看不中用。
- 儘量在做一些大內存分配等可疑內存操作時進行try catch操作,避免不必要的應用閃退。
- 儘量的優化自己的代碼,減少冗餘,進行編譯打包等優化對齊處理,避免類加載時浪費內存。
- 關閉資源對象,比如數據庫操作中得Cursor,IO操作的對象
- 調用了registerReceiver註冊廣播後,調用unregisterReceiver()來取消
- 調用了View.getViewTreeObserver().addOnXXXListener, 調用View.getViewTreeObserver().removeXXXListener
- 隨時注意Context的使用是否會導致內存泄漏
現在開始看看,怎麼來分析內存泄漏
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 就是自動地顯示一個通知。
工作機制
- RefWatcher.watch() 創建一個 KeyedWeakReference 到要被監控的對象。
- 然後在後臺線程檢查引用是否被清除,如果沒有,調用GC。
- 如果引用還是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。
- 在另外一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件。
- 得益於唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄露。
- HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並確定是否是泄露。如果是的話,建立導致泄露的引用鏈。
- 引用鏈傳遞到 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文件按鈕