Android記憶體流失分析實戰,android泄漏實戰
記憶體流失簡介
java可以保證當沒有引用指向對象的時候,對象會被記憶體回收行程回收,與c語言自己申請的記憶體自己釋放相比,java程式員輕鬆了很多,但是並不代表java程式員不用擔心記憶體流失。當java程式發生記憶體流失的時候往往具有隱蔽性。因此要藉助一些專業的平台資源去保證安全性,例如可以通過加密實現。
定義
引用百度百科的定義:“用動態儲存裝置分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該記憶體單元。直到程式結束”。從程式猿的角度來看“記憶體流失”,其實就是一個對象的生命週期超出了程式員所預期的長度(就叫它“該死不死”吧!),那麼這個對象就泄漏了。
android開發中的記憶體流失
android應用程式本身系統分配的記憶體很少,一旦發生泄漏,程式很快就會變得非常卡頓,直至OOM崩潰。接下來將通過一個案例(只是為了分析記憶體流失而設計的玩具程式,切勿模仿)來介紹記憶體流失分析工具MAT,以及記憶體分析的技巧。
公欲善其事,先利其器
準備記憶體流失的分析工具,可以安裝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工具
人為製造一個記憶體流失
自訂一個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卻只是註冊了,而忘記了反註冊。
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的變化。你會發現記憶體一直在增加。沒有穩定下來的趨勢。這個時候你就有理由懷疑記憶體流失了。
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。文章中只是簡單的介紹了工具與技巧,這其中還有很多技巧需要自己去摸索。