簡析Android的垃圾回收與內存泄露

Android系統是運行在Java虛擬機上的,作為嵌入式設備,內存往往非常有限,了解Android的垃圾回收機制,可以有效的防止內存泄露問題或者OOM問題。本文作為入門文章,將淺顯的討論垃圾回收與內存泄露的原理,不討論Dalvik虛擬機底層機制或者native層面的問題。

1. 基礎

在分析垃圾回收前,我們要復習Java與離散數學的基礎。

  • 實例化:對象是類的一個實例,創建對象的過程也叫類的實例化。對象是以類為模板來創建的。比如Car car = new Car();,我們就創造了一個Car的實例(Create new class instance of Car)
  • 引用:某些對象的實例化需要其它的對象實例,比如ImageView的實例化就需要Context對象,就是表示ImageView對于Context持有引用(ImageView holds a reference to Context)。
  • 有向圖:在每條邊上都標有有向線段的圖稱為有向圖,Java中的garbage collection采用有向圖的方式進行內存管理,箭頭的方向表示引用關系,比如 B ← A ,就是B中需要A,B引用A。
  • 可達:有向圖D={S,R}中,對于Si,Sj 屬于S,如果從Si到Sj有任何一條通路存在,則可稱Si可達Sj。也就是說,當B ← A中間箭頭斷了,就稱作不可達,這時A就不可達B了。
  • Shallow heap 與 Retain heap 的對比
    • Shallow heap表示當前對象所消耗的內存
    • Retained heap表示當前對象所消耗的內存加上它引用的內存總合 

Google I/O 2011: Memory management for Android Apps

上圖的橙色的Object是該有向圖的起點,它的Shallow heap是100,而它的Retained heap是100 + 300 = 400。

2. 什么是垃圾回收

Java GC(Garbage Collection,垃圾收集,垃圾回收)機制,是Java與C++/C的主要區別之一,作為Java開發者,一般不需要專門編寫內存回收和垃圾清理代碼,對內存泄露和溢出的問題,也不需要像C程序員那樣戰戰兢兢。這是因為在Java虛擬機中,存在自動內存管理和垃圾清掃機制。概括地說,該機制對虛擬機中的內存進行標記,并確定哪些內存需要回收,根據一定的回收策略,自動的回收內存,永不停息(Nerver Stop)的保證虛擬機中的內存空間,防止出現內存泄露和溢出問題。

3. 什么情況需要垃圾回收

對于GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常GC采用有向圖的方式記錄并管理堆中的所有對象,通過這種方式確定哪些對象時“可達”,哪些對象時“不可達”。當對象不可達的時候,即對象不再被引用的時候,就會被垃圾回收。

網上有很多文檔介紹可達的關系了,如圖,在第六行的時候,o2改變了指向,Obj2就不再引用main的了,即他它們是不可達的,Obj2就可能在下次的GC中被回收。

developerWorks Java technology

4. 什么是內存泄露

當你不再需要某個實例后,但是這個對象卻仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。這個情況就叫做內存泄露(Memory Leak)。

下面將以How to Leak a Context: Handlers & Inner Classes這篇文章翻譯為例,介紹一個內存泄露。

看如下的代碼

public class SampleActivity extends Activity { 
 
  private final Handler mLeakyHandler = new Handler() {    @Override 
    public void handleMessage(Message msg) {      // ...  
    } 
  } 
}  

當你打完這個代碼后,IDE應該就會提醒你

In Android, Handler classes should be static or leaks might occur. 

它到底是如何泄露的呢?

  1. 當你啟動一個application時,它會自動在主線程創建一個Looper對象,用于處理Handler中的message。Looper實現了簡單的消息隊列,在循環中一個接一個的處理Message對象。大多數Application框架事件(比如Activity生命周期調用,按鈕點擊等)都在Message中,它們在Looper的消息隊列中一個接一個的處理。注意Looper是存在于application整個生命周期中。
  2. 當你新建了一個handler對象后,它會被分配給Looper的消息隊列。被發送到消息隊列的Message將保持對Handler的引用,因為當消息隊列處理到這個消息時,需要使用[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)這個方法。(也就是說,只要沒有處理到這個Message,Handler就一直在隊列中被引用)
  3. 在java中,非靜態的內部Class與匿名Class對它們外部的Class有強引用。static inner class除外。 

引用關系

現在,我們嘗試運行如下代碼

public class SampleActivity extends Activity { 
 
  private final Handler mLeakyHandler = new Handler() {    @Override 
    public void handleMessage(Message msg) {      // ... 
    } 
  }  @Override 
  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    // Post a message and delay its execution for 10 minutes. 
    mLeakyHandler.postDelayed(new Runnable() {      @Override 
      public void run() { /* ... */ } 
    }, 1000 * 60 * 10);    // Go back to the previous Activity. 
    finish(); 
  } 
}  

這個程序很簡單,我們可以腦補一下,它應該是啟動了又瞬間關閉,但是事實真的是關閉了嗎?

稍有常識的人可以看出,它發送了一個Message,將在十分鐘后運行,也就是說Message將被保持引用達到10分鐘,這就照成了至少10分鐘的內存泄露。

最后正確的代碼如下

ublic class SampleActivity extends Activity { 
 
  /** 
   * Instances of static inner classes do not hold an implicit 
   * reference to their outer class. 
   */ 
  private static class MyHandler extends Handler { 
    private final WeakReference<SampleActivity> mActivity; 
 
    public MyHandler(SampleActivity activity) { 
      mActivity = new WeakReference<SampleActivity>(activity); 
    }    @Override 
    public void handleMessage(Message msg) {      SampleActivity activity = mActivity.get();      if (activity != null) {        // ... 
      } 
    } 
  }  private final MyHandler mHandler = new MyHandler(this);  /** 
   * Instances of anonymous classes do not hold an implicit 
   * reference to their outer class when they are "static". 
   */ 
  private static final Runnable sRunnable = new Runnable() {      @Override 
      public void run() { /* ... */ } 
  };  @Override 
  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    // Post a message and delay its execution for 10 minutes. 
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);    // Go back to the previous Activity. 
    finish(); 
  } 
}  

結論

  • GC是按照有向圖是否可達來判斷對象實例是否有用
  • 如果不在需要某個實例,卻仍然被引用,這個情況叫做內存泄露
  • 匿名類/非靜態類內部class會保持對它所在Activity的引用,使用時要注意它們的生命周期不能超過Activity,否則要用static inner class
  • 善于在Activy中的生命周期(比如onPause)中手動控制其他類的生命周期

最后再補充一下iOS的情況,iOS在新版的OC與Swift中,已經引入了新的內存管理體系ARC(auto reference counting,引用自動計數),C代碼在編譯時,編譯器自動適時的添加釋放內存的代碼。

 

 

來自:http://mobile.51cto.com/android-531863.htm

 

 本文由用戶 tkqc7649 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!