簡析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.
它到底是如何泄露的呢?
- 當你啟動一個application時,它會自動在主線程創建一個Looper對象,用于處理Handler中的message。Looper實現了簡單的消息隊列,在循環中一個接一個的處理Message對象。大多數Application框架事件(比如Activity生命周期調用,按鈕點擊等)都在Message中,它們在Looper的消息隊列中一個接一個的處理。注意Looper是存在于application整個生命周期中。
- 當你新建了一個handler對象后,它會被分配給Looper的消息隊列。被發送到消息隊列的Message將保持對Handler的引用,因為當消息隊列處理到這個消息時,需要使用[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)這個方法。(也就是說,只要沒有處理到這個Message,Handler就一直在隊列中被引用)
- 在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