Android內存泄漏分析實戰

內存泄漏簡介

java能夠保證當沒有引用指向對象的時候,對象會被垃圾回收器回收,與c語言本身申請的內存本身釋放相比,java程序員輕鬆了不少,可是並不表明java程序員不用擔憂內存泄漏。當java程序發生內存泄漏的時候每每具備隱蔽性。所以要藉助一些專業的平臺資源去保證安全性,例如能夠經過加密實現javascript

定義

引用百度百科的定義:「用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果致使一直佔據該內存單元。直到程序結束」。從程序猿的角度來看「內存泄漏」,其實就是一個對象的生命週期超出了程序員所預期的長度(就叫它「該死不死」吧!),那麼這個對象就泄漏了。java

android開發中的內存泄漏

android應用程序自己系統分配的內存不多,一旦發生泄漏,程序很快就會變得很是卡頓,直至OOM崩潰。接下來將經過一個案例(只是爲了分析內存泄漏而設計的玩具程序,切勿模仿)來介紹內存泄漏分析工具MAT,以及內存分析的技巧。android

公欲善其事,先利其器

準備內存泄漏的分析工具,能夠安裝eclipse插件mat。若是eclise安裝mat不成功,那多是缺乏必要的libs,若是嫌找庫麻煩,能夠只勾選第二項安裝,不過會缺乏某些功能,可是也夠用了。 
在線安裝:
http://download.eclipse.org/mat/1.4/update-site/ 
下載安裝:
http://mirror.hust.edu.cn/eclipse//mat/1.4/MemoryAnalyzer-1.4.0.201406041413.zip程序員

mat插件如何使用

若是已經成功安裝好了mat工具,使用起來很是簡單,首先將須要分析的應用程序跑起來,打開eclipse的devices視圖你將會看到點擊「Dump Hprof file」按鈕,注意點擊一下就能夠了,而後等待(等待幾秒)dump一個內存快照出來,接下來就會自動打開mat的視圖了,若是mat沒有安裝成功,會讓你保存一個.hprof文件到本地。看看下面的圖例吧數據庫

dump hprof啓動mat工具json

人爲製造一個內存泄漏

自定義一個ActivityManager,提供兩個方法,分別用來註冊與反註冊Activity。源碼下載安全

public class ActivityManager {
   private List<Activity> mActivities = new ArrayList<>();
   private static ActivityManager sInstance;

   private ActivityManager() {
   };

   public static ActivityManager instance() {
       if (sInstance == null) {
           sInstance = new ActivityManager();
       }

       return sInstance;
   }

   public void registActivity(Activity activity) {
       mActivities.add(activity);
   }

   public void unRigistActivity(Activity activity) {
       mActivities.remove(activity);
   }
}

在MainActivity的onCreate與onDestroy中分別調用registActivity和registActivity方法進行註冊與反註冊。可是OtherActivity卻只是註冊了,而忘記了反註冊。eclipse

public class MainActivity extends Activity {
   public static final String TAG = MainActivity.class.getSimpleName();

   private Button mBtn;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mBtn = (Button) findViewById(R.id.btn);
       mBtn.setOnClickListener(new OnClickListener() {

           @Override
           public void onClick(View v) {
               Intent intent = new Intent();
               intent.setClass(MainActivity.this, OtherActivity.class);
               startActivity(intent);
           }
       });
       
       ActivityManager.instance().registActivity(this);
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
       
       ActivityManager.instance().registActivity(this);
   }

public class OtherActivity extends Activity {
   public static final String TAG = OtherActivity.class.getSimpleName();

   private Object[] mObjs = new Object[10000];//模擬快速消耗內存,使效果明顯
   private Button mBtn;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_other);
       mBtn = (Button) findViewById(R.id.btn);
       mBtn.setOnClickListener(new OnClickListener() {

           @Override
           public void onClick(View v) {
               for (int i = 0; i < mObjs.length; i++) {
                   mObjs[i] = new Object();
               }
               
               finish();
           }
       });

       ActivityManager.instance().registActivity(this);
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
   }
}

案例中的內存泄漏是人爲構造的,因此咱們事先已經知道有泄漏了,可是實際的開發過程當中,內存泄漏是隱蔽的,一開始咱們並不知道,因此咱們須要經過一些手段來測試APP是否有內存泄漏。首先在Devices視圖中選中須要測試的進程,而後點擊Device視圖面板的Update Heap按鈕,而後打開Heap視圖,點擊Cause GC。而後反覆的在MainActivyt和OtherActivity之間切換。觀察Heap size的變化。你會發現內存一直在增長。沒有穩定下來的趨勢。這個時候你就有理由懷疑內存泄漏了。ide

Update heap觀察heap size等變化狀況函數

找出泄漏的對象

按照前面mat的使用步驟,dump一個內存快照出來,而後從分析報告中點擊「Leak suspects」這裏會列車可能泄漏的對象,其中你會發現「 com.vjson.leaks.OtherActivity」的身影。OtherActivity這個類有33個實例,做爲代碼的生產者,你應該一會兒就會發現,原來是OtherActivity泄漏了。發現它泄漏以後,如何找出是哪個對象持有了OtherActivity對象的引用呢?

可能泄漏的報告

找出引用鏈

使用OQL對象查詢語言查詢出泄漏的對象,寫過SQL的同窗必定對她有一種既陌生又熟悉的感受,和SQL很是類似,語法簡單易懂,可是很是強大select *from com.vjson.leaks.OtherActivity賽選出OtherActivity這一類對象,而後選擇「exclude weak/soft references」賽選出除了軟引用和弱引用以外的對象,也就是強引用了!。對象的引用類型不在本文的講解範圍,可是你必定要知道「強引用」,「軟引用」,「弱引用」。「幽靈引用」,若是不知道自行腦補去吧!

OQL對象查詢找出引用鏈

對象引用鏈

而後找出GC的根節點,從圖二種能夠看出,原來Activity對象被ActivityManager裏面的ArrayList給hold住了,因此接下來的工做就是在OtherActivity的onDestroy中反註冊,內存泄漏就被解決了。

Android開發中常見的內存泄漏

對象沒有反註冊

數據庫cursor沒有關閉

Bitmap沒有回收

ListView item沒有複用

Handler在Activity中定義爲非static的匿名內部類

總結

若是耐心的看完本文,那麼恭喜你媽媽不再用擔憂內存泄漏了。其實只要掌握了分析問題的技巧與工具,內存泄漏so easy。文章中只是簡單的介紹了工具與技巧,這其中還有不少技巧須要本身去摸索。 

相關文章
相關標籤/搜索