關於Android 的內存泄露及分析

1、 Android的內存機制
Android的程序由Java語言編寫,因此Android的內存管理與Java的內存管理類似。程序員經過new爲對象分配內存,全部對象在java堆內分配空間;然而對象的釋放是由垃圾回收器來完成的.
那麼GC怎麼可以確認某一個對象是否是已經被廢棄了呢?Java採用了有向圖的原理。Java將引用關係考慮爲圖的有向邊,有向邊從引用者指向引用對象。線程對象能夠做爲有向圖的起始頂點,該圖就是從起始頂點開始的一棵樹,根頂點能夠到達的對象都是有效對象,GC不會回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個(這些)對象再也不被引用,能夠被GC回收。java


2、Android的內存溢出
android

一、內存泄露致使程序員

因爲咱們程序的失誤,長期保持某些資源(如Context)的引用,形成內存泄露,資源形成得不到釋放。 數據庫

Android 中常見就是Activity 被引用沒有在調用finish以後卻沒有釋放,第二次打開activity 又從新建立,這樣的內存泄露則會致使內存的溢出。
二、佔用內存較多的對象編程

 保存了多個耗用內存過大的對象(如Bitmap)或加載單個超大的圖片,形成內存超出限制。緩存

3、常見的內存泄漏
1.萬惡的static
  static是Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。eclipse

    private static Activity mContext;       //省略 ide

 如何纔能有效的避免這種引用的發生呢?函數

    第一,應該儘可能避免static成員變量引用資源耗費過多的實例,好比Context。工具

    第2、Context儘可能使用Application Context,由於Application的Context的生命週期比較長,引用它不會出現內存泄露的問題。

    第3、使用WeakReference代替強引用。好比可使用WeakReference<Context> mContextRef;

2.線程惹的禍
線程也是形成內存泄露的一個重要的源頭。線程產生內存泄露的主要緣由在於線程生命週期的不可控。咱們來考慮下面一段代碼。

[java]  view plain copy
  1. public class MyActivity extends Activity {       
  2. @Override       
  3. public void onCreate(Bundle savedInstanceState) {           
  4.   super.onCreate(savedInstanceState);           
  5.   setContentView(R.layout.main);           
  6.   new MyThread().start();       
  7. }         
  8. private class MyThread extends Thread{           
  9. @Override           
  10.   public void run() {               
  11.   super.run();               
  12.   //do somthing           
  13. }       
  14. }   
  15. }    
   咱們思考一個問題:假設MyThread的run函數是一個很費時的操做,當調用finish的時候Activity 會銷燬掉嗎?

   事實上因爲咱們的線程是Activity的內部類,因此MyThread中保存了Activity的一個引用,當MyThread的run函數沒有結束時,MyThread是不會被銷燬的,所以它所引用的老的Activity也不會被銷燬,所以就出現了內存泄露的問題。

 

解決方案


    第1、將線程的內部類,改成靜態內部類。

    第2、若是須要引用Acitivity,使用弱引用。
    
    另外在使用handler 的時候, 尤爲用到循環調用的時候,在Activity 退出的時候注意移除。不然也會致使泄露

   

[java]  view plain copy
  1. public class ThreadDemo extends Activity {    
  2.     private static final String TAG = "ThreadDemo";    
  3.     private int count = 0;    
  4.     private Handler mHandler =  new Handler();    
  5.         
  6.     private Runnable mRunnable = new Runnable() {    
  7.             
  8.         public void run() {    
  9.             //爲了方便 查看,咱們用Log打印出來     
  10.             Log.e(TAG, Thread.currentThread().getName() + " " +count);      
  11.             //每2秒執行一次     
  12.             mHandler.postDelayed(mRunnable, 2000);    
  13.         }     
  14.     };    
  15.     @Override    
  16.     public void onCreate(Bundle savedInstanceState) {    
  17.         super.onCreate(savedInstanceState);    
  18.         setContentView(R.layout.main);     
  19.         //經過Handler啓動線程     
  20.         mHandler.post(mRunnable);    
  21.     }    
  22.         
  23. }  
  24. //因此咱們在應用退出時,要將線程銷燬,咱們只要在Activity中的,onDestory()方法處理一下就OK了,以下代碼所示:  
  25. @Override    
  26.   protected void onDestroy() {    
  27.     mHandler.removeCallbacks(mRunnable);    
  28.     super.onDestroy();    
  29.   }   
  30.    

3.Bitmap
能夠說出現OutOfMemory問題的絕大多數人,都是由於Bitmap的問題。由於Bitmap佔用的內存實在是太多了,特別是分辨率大的圖片,若是要顯示多張那問題就更顯著了。


    解決方案:

    第1、及時的銷燬。

    雖然,系統可以確認Bitmap分配的內存最終會被銷燬,可是因爲它佔用的內存過多,因此極可能會超過java堆的限制。所以,在用完Bitmap時,要及時的recycle掉。recycle並不能肯定當即就會將Bitmap釋放掉,可是會給虛擬機一個暗示:「該圖片能夠釋放了」。

    第2、設置必定的採樣率。

    有時候,咱們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只須要記載一個縮小過的圖片,這時候能夠設置必定的採樣率,那麼就能夠大大減少佔用的內存。以下面的代碼:

private ImageView preview;  

BitmapFactory.Options options = new BitmapFactory.Options();  

options.inSampleSize = 2;//圖片寬高都爲原來的二分之一,即圖片爲原來的四分之一  

Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);  preview.setImageBitmap(bitmap); 

第3、巧妙的運用軟引用(SoftRefrence)

    有些時候,咱們使用Bitmap後沒有保留對它的引用,所以就沒法調用Recycle函數。這時候巧妙的運用軟引用,可使Bitmap在內存快不足時獲得有效的釋放。
  

4.行蹤詭異的Cursor

    Cursor是Android查詢數據後獲得的一個管理數據集合的類,正常狀況下,若是查詢獲得的數據量較小時不會有內存問題,並且虛擬機可以保證Cusor最終會被釋放掉。

    然而若是Cursor的數據量特表大,特別是若是裏面有Blob信息時,應該保證Cursor佔用的內存被及時的釋放掉,而不是等待GC來處理。而且Android明顯是傾向於編程者手動的將Cursor close掉

  並且android數據庫中對Cursor資源的是又限制個數的,若是不及時close掉,會致使別的地方沒法得到
    
5.構造Adapter時,沒有使用緩存的 convertView 
  以構造ListView的BaseAdapter爲例,在BaseAdapter中提升了方法:
   public View getView(int position, View convertView, ViewGroup parent)

  AdapterView 在使用View會有一個循環的View隊列的,把不顯示的View從新投入使用,因此在convertView不爲空的時候,不要直接建立新的View



小結:

static:引用了大對象如context

線程:切屏時Activity由於線程引用而沒有如期被銷燬;handler有關,Activity意外終止但線程還在

Bitmap:要及時recycle,下降採樣率

Cursor:要及時關閉

Adapter:沒有使用緩存的convertView

 

4、內存泄漏調試:
(1).內存監測工具 DDMS --> Heap
 用 Heap監測應用進程使用內存狀況的步驟以下:
      1. 切換到DDMS透視圖,並確認Devices視圖、Heap視圖都是打開的;
      2. 正常與手機連接成功後,在DDMS的Devices視圖中將會顯示手機設備的序列號,以及設備中正在運行的部分進程信息;
      3. 點擊選中想要監測的進程
      4. 點擊選中Devices視圖界面中最上方一排圖標中的「Update Heap」圖標;
      5. 點擊Heap視圖中的「Cause GC」按鈕;
      6. 此時在Heap視圖中就會看到當前選中的進程的內存使用量的詳細狀況。
   Heap視圖界面會定時刷新,在對應用的不斷的操做過程當中就能夠看到內存使用的變化;
  如何才能知道咱們的程序是否有內存泄漏的可能性呢。這裏須要注意一個值:Heap視圖中部有一個Type叫作data object,即數據對象,也就是咱們的程序中大量存在的類類型的對象。在data object一行中有一列是「Total Size」,其值就是當前進程中全部Java數據對象的內存總量,通常狀況下,這個值的大小決定了是否會有內存泄漏。能夠這樣判斷:
   a) 不斷的操做當前應用,同時注意觀察data object的Total Size值;
   b) 正常狀況下Total Size值都會穩定在一個有限的範圍內,也就是說因爲程序中的的代碼良好,沒有形成對象不被垃圾回收的狀況,因此說  雖然咱們不斷的操做會不斷的生成不少對象,而在虛擬機不斷的進行GC的過程當中,這些對象都被回收了,內存佔用量會會落到一個穩定的水平;
    c) 反之若是代碼中存在沒有釋放對象引用的狀況,則data object的Total Size值在每次GC後不會有明顯的回落,隨着操做次數的增多Total Size的值會愈來愈大,

(2)內存分析工具 MAT(Memory Analyzer Tool)
  這裏介紹一個極好的內存分析工具 -- Memory Analyzer Tool(MAT)。
  MAT是一個Eclipse插件,同時也有單獨的RCP客戶端。官方下載地址、MAT介紹和詳細的使用教程請參見:www.eclipse.org/mat,在此不進行說明了。另外在MAT安裝後的幫助文檔裏也有完備的使用教程。在此僅舉例說明其使用方法。我本身使用的是MAT的eclipse插件,使用插件要比RCP稍微方便一些。插件安裝成功後,分析步驟(安裝方法有多重,你們隨便)
 
   (a) 生成.hprof文件
  
      1. 打開eclipse並切換到DDMS
      2. 點擊選中想要分析的應用的進程,在Devices視圖上方的一行圖標按鈕中,選中「Update Heap」。
      3. 當內存你感受異常的時候,按下「Dump HPROF file」按鈕,這個時候會提示設置hprof文件的保存路徑。
(二) 使用MAT導入.hprof文件
     1. 經過/ANDROID_SDK/tools目錄下的hprof-conv.exe工具(使用命令同adb),輸入命令hprof-conv xxx.hprof yyy.hprof,其中xxx.hprof爲原始文件,yyy.hprof爲轉換事後的文件。
    2. 在Eclipse中點擊Windows->Open Perspective->Other->Memory Analysis perspective界面。在MAT中點擊File->Open File,瀏覽並導入剛剛 轉換而獲得的.hprof文件。
(三) 使用MAT的視圖工具分析內存
  導入.hprof文件之後,MAT會自動解析並生成報告,報告中會列出使用內存過多或者初始化的實例過多的類。

 點擊Dominator Tree,並按Package分組,選擇報告中提到的可疑實例的類,在彈出菜單中選擇List objects->With incoming references。這時會列出全部可疑類,右鍵點擊某一項,並選擇Path to GC Roots -> exclude weak/soft references,會進一步篩選出跟程序相關的全部有內存泄露的類。據此,能夠追蹤到代碼中的某一個產生泄露的類。
   主要是看可疑類的引用是由於什麼代碼的引用而致使沒法釋放的
  總之使用MAT分析內存查找內存泄漏的根本思路,就是找到哪一個類的對象的引用沒有被釋放,找到沒有被釋放的緣由,也就能夠很容易定位代碼中的哪些片斷的邏輯有問題了。

相關文章
相關標籤/搜索