1. 程式人生 > >Android中的記憶體洩漏和記憶體溢位問題

Android中的記憶體洩漏和記憶體溢位問題

記憶體洩漏簡單地說就是申請了一塊記憶體空間,使用完畢後沒有釋放掉。它的一般表現方式是程式執行時間越長,佔用記憶體越多,最終用盡全部記憶體,整個系統崩潰。由程式申請的一塊記憶體,且沒有任何一個指標指向它,那麼這塊記憶體就洩露了。
從使用者使用程式的角度來看,記憶體洩漏本身不會產生什麼危害,作為一般的使用者,根本感覺不到記憶體洩漏的存在。真正有危害的是記憶體洩漏的堆積,這會最終消耗盡系統所有的記憶體。從這個角度來說,一次性記憶體洩漏並沒有什麼危害,因為它不會堆積,而隱式記憶體洩漏危害性則非常大,因為較之於常發性和偶發性記憶體洩漏它更難被檢測到。

Android應用記憶體洩漏的的原因有以下幾個:
1查詢資料庫後沒有關閉遊標cursor
2 構造Adapter時,沒有使用 convertView 重用
3 Bitmap物件不在使用時呼叫recycle()釋放記憶體
4 物件被生命週期長的物件引用,如activity被靜態集合引用導致activity不能釋放
記憶體洩漏的發現:
通過DDMS中的heap工具,去發現是否有記憶體溢位。

記憶體洩漏如何解決:
通過記憶體分析工具 MAT(Memory Analyzer Tool),找到記憶體洩露的物件

  安卓的虛擬機器是基於暫存器的Dalvik,它的最大堆大小一般是16M。但是安卓採用的是Java語言編寫,所以在很大程度上,安卓的記憶體機制等同於Java的記憶體機制,在剛開始開發的時候,記憶體的限制問題會給我們帶來記憶體溢位等嚴重問題。在我們不使用一些記憶體的時候,我們要儘量在Android或者其他平臺上避免在執行其他程式時,儲存必要的狀態,使得一些死程序所帶來的記憶體問題,應該儘量在關閉程式或者儲存狀態的時候釋放掉,這樣能提高系統在執行方面的流暢性。

  安卓的記憶體主要表現在:

  1. 在Android平臺上,長期保持一些資源的引用,造成一些記憶體不能釋放,帶來的記憶體洩露問題很多。比如:Context(下文中提到的Activity都是Context),在一些你需要保持你的首個類物件狀態,並且把狀態傳入其他類物件中時,這樣消除掉首個類物件之前,你必須先把接收類物件釋放掉。需要注意一點的是:因為在Java或者Android記憶體機制中,頂點的結點釋放前必須保證其他物件沒有呼叫才能被系統GC回收釋放。我們來看一段程式碼:

@Override

  protected void onCreate(Bundle state) {

  super.onCreate(state);

  TextViewlabel = new
TextView(this);   label.setText("Leaksare bad");   setContentView(label);   }

  這個程式碼的意思就是我們把一個TextView的例項載入到了我們正在執行的Activity(Context)當中,因此,通過GC回收機制,我們知道,要釋放Context,就必須先釋放掉引用他的一些物件。如果沒有,那在要釋放Context的時候,你會發現會有大量的記憶體溢位。所以在你不小心的情況下記憶體溢位是一件非常容易的事情。 儲存一些物件時,同時也會造成記憶體洩露。最簡單的比如說點陣圖(Bitmap),比如說:在螢幕旋轉時,會破壞當前保持的一個Activity狀態,並且重新申請生成新的Activity,直到新的Activity狀態被儲存。我們再看一段程式碼:

privatestatic Drawable sBackground;

  @Override

  protected void onCreate(Bundle state) {

  super.onCreate(state);

  TextView label = new TextView(this);

  label.setText("Leaks are bad");

  if (sBackground == null) {

  sBackground =getDrawable(R.drawable.large_bitmap);

  }

  label.setBackgroundDrawable(sBackground);

  setContentView(label);

  }

  這個程式碼是非常快的同時也是錯誤的。它的記憶體洩露很容易出在螢幕轉移的方向上。雖然我們會發現沒有顯示的儲存Context這個例項,但是當我們把繪製的圖連線到一個檢視的時候,Drawable就會將被View設定為回撥,這就說明,在上述的程式碼中,其實在繪製TextView到活動中的時候,我們已經引用到了這個Activity。連結情況可以表現為:Drawable->TextView->Context。

  所以在想要釋放Context的時候,其實還是儲存在記憶體中,並沒有得到釋放。

  如何避免這種情況:主要在於。執行緒最容易出錯。大家不要小看執行緒,在Android裡面執行緒最容易造成記憶體洩露。執行緒產生記憶體洩露的主要原因在於執行緒生命週期的不可控。下面有一段程式碼:

publicclass MyTest extends Activity {

  @Override

  publicvoid onCreate(BundlesavedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.main);

  new MyThread().start();

  }

  privateclass MyThread extends Thread{

  @Override

  public void run() {

  super.run();

  //do somthing

  }

  }

  }

  程式碼很簡單,但是在Android上又來新問題了,當我們在切換檢視螢幕的時候(橫豎屏),就會重新建立橫屏或者豎屏的Activity。我們形象的認為之前建立的Activity會被回收,但是事實如何呢?Java機制不會給你同樣的感受,在我們釋放Activity之前,因為run函式沒有結束,這樣MyThread並沒有銷燬,因此引用它的Activity(Mytest)也有沒有被銷燬,因此也帶來的記憶體洩露問題。

  有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread只有在run函式不結束時才出現這種記憶體洩露問題,然而AsyncTask內部的實現機制是運用了ThreadPoolExcutor,該類產生的Thread物件的生命週期是不確定的,是應用程式無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現記憶體洩露的問題。

  執行緒問題的改進方式主要有:

  l 將執行緒的內部類,改為靜態內部類。

  l 在程式中儘量採用弱引用儲存Context。

  2. 萬惡的bitmap。。。

  Bitmap是一個很萬惡的物件,對於一個記憶體物件,如果該物件所佔記憶體過大,在超出了系統的記憶體限制時候,記憶體洩露問題就很明顯了。。

  解決bitmap主要是要解決在記憶體儘量不儲存它或者使得采樣率變小。在很多場合下,因為我們的圖片畫素很高,而對於手機螢幕尺寸來說我們並不用那麼高畫素比例的圖片來載入時,我們就可以先把圖片的取樣率降低在做原來的UI操作。

  如果在我們不需要儲存bitmap物件的引用時候,我們還可以用軟引用來做替換。具體的例項程式碼google上面也有很多。

  綜上所述,要避免記憶體洩露,主要要遵循以下幾點:

  第一:不要為Context長期儲存引用(要引用Context就要使得引用物件和它本身的生命週期保持一致)。

  第二:如果要使用到Context,儘量使用ApplicationContext去代替Context,因為ApplicationContext的生命週期較長,引用情況下不會造成記憶體洩露問題

  第三:在你不控制物件的生命週期的情況下避免在你的Activity中使用static變數。儘量使用WeakReference去代替一個static。

  第四:垃圾回收器並不保證能準確回收記憶體,這樣在使用自己需要的內容時,主要生命週期和及時釋放掉不需要的物件。儘量在Activity的生命週期結束時,在onDestroy中把我們做引用的其他物件做釋放,比如:cursor.close()。

  其實我們可以在很多方面使用更少的程式碼去完成程式。比如:我們可以多的使用9patch圖片等。有很多細節地方都可以值得我們去發現、挖掘更多的記憶體問題。我們要是能做到C/C++對於程式的“誰建立,誰釋放”原則,那我們對於記憶體的把握,並不比Java或Android本身的GC機制差,而且更好的控制記憶體,能使我們的手機執行得更流暢。