簡析Android的垃圾回收與內(nèi)存泄露
Android系統(tǒng)是運(yùn)行在Java虛擬機(jī)上的,作為嵌入式設(shè)備,內(nèi)存往往非常有限,了解Android的垃圾回收機(jī)制,可以有效的防止內(nèi)存泄露問題或者OOM問題。本文作為入門文章,將淺顯的討論垃圾回收與內(nèi)存泄露的原理,不討論Dalvik虛擬機(jī)底層機(jī)制或者native層面的問題。
1. 基礎(chǔ)
在分析垃圾回收前,我們要復(fù)習(xí)Java與離散數(shù)學(xué)的基礎(chǔ)。
- 實(shí)例化:對(duì)象是類的一個(gè)實(shí)例,創(chuàng)建對(duì)象的過程也叫類的實(shí)例化。對(duì)象是以類為模板來創(chuàng)建的。比如Car car = new Car();,我們就創(chuàng)造了一個(gè)Car的實(shí)例(Create new class instance of Car)
- 引用:某些對(duì)象的實(shí)例化需要其它的對(duì)象實(shí)例,比如ImageView的實(shí)例化就需要Context對(duì)象,就是表示ImageView對(duì)于Context持有引用(ImageView holds a reference to Context)。
- 有向圖:在每條邊上都標(biāo)有有向線段的圖稱為有向圖,Java中的garbage collection采用有向圖的方式進(jìn)行內(nèi)存管理,箭頭的方向表示引用關(guān)系,比如 B ← A ,就是B中需要A,B引用A。
- 可達(dá):有向圖D={S,R}中,對(duì)于Si,Sj 屬于S,如果從Si到Sj有任何一條通路存在,則可稱Si可達(dá)Sj。也就是說,當(dāng)B ← A中間箭頭斷了,就稱作不可達(dá),這時(shí)A就不可達(dá)B了。
- Shallow heap 與 Retain heap 的對(duì)比
- Shallow heap表示當(dāng)前對(duì)象所消耗的內(nèi)存
- Retained heap表示當(dāng)前對(duì)象所消耗的內(nèi)存加上它引用的內(nèi)存總合
Google I/O 2011: Memory management for Android Apps
上圖的橙色的Object是該有向圖的起點(diǎn),它的Shallow heap是100,而它的Retained heap是100 + 300 = 400。
2. 什么是垃圾回收
Java GC(Garbage Collection,垃圾收集,垃圾回收)機(jī)制,是Java與C++/C的主要區(qū)別之一,作為Java開發(fā)者,一般不需要專門編寫內(nèi)存回收和垃圾清理代碼,對(duì)內(nèi)存泄露和溢出的問題,也不需要像C程序員那樣戰(zhàn)戰(zhàn)兢兢。這是因?yàn)樵贘ava虛擬機(jī)中,存在自動(dòng)內(nèi)存管理和垃圾清掃機(jī)制。概括地說,該機(jī)制對(duì)虛擬機(jī)中的內(nèi)存進(jìn)行標(biāo)記,并確定哪些內(nèi)存需要回收,根據(jù)一定的回收策略,自動(dòng)的回收內(nèi)存,永不停息(Nerver Stop)的保證虛擬機(jī)中的內(nèi)存空間,防止出現(xiàn)內(nèi)存泄露和溢出問題。
3. 什么情況需要垃圾回收
對(duì)于GC來說,當(dāng)程序員創(chuàng)建對(duì)象時(shí),GC就開始監(jiān)控這個(gè)對(duì)象的地址、大小以及使用情況。通常GC采用有向圖的方式記錄并管理堆中的所有對(duì)象,通過這種方式確定哪些對(duì)象時(shí)“可達(dá)”,哪些對(duì)象時(shí)“不可達(dá)”。當(dāng)對(duì)象不可達(dá)的時(shí)候,即對(duì)象不再被引用的時(shí)候,就會(huì)被垃圾回收。
網(wǎng)上有很多文檔介紹可達(dá)的關(guān)系了,如圖,在第六行的時(shí)候,o2改變了指向,Obj2就不再引用main的了,即他它們是不可達(dá)的,Obj2就可能在下次的GC中被回收。
developerWorks Java technology
4. 什么是內(nèi)存泄露
當(dāng)你不再需要某個(gè)實(shí)例后,但是這個(gè)對(duì)象卻仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。這個(gè)情況就叫做內(nèi)存泄露(Memory Leak)。
下面將以How to Leak a Context: Handlers & Inner Classes這篇文章翻譯為例,介紹一個(gè)內(nèi)存泄露。
看如下的代碼
- public class SampleActivity extends Activity {
- private final Handler mLeakyHandler = new Handler() { @Override
- public void handleMessage(Message msg) { // ...
- }
- }
- }
當(dāng)你打完這個(gè)代碼后,IDE應(yīng)該就會(huì)提醒你
- In Android, Handler classes should be static or leaks might occur.
它到底是如何泄露的呢?
- 當(dāng)你啟動(dòng)一個(gè)application時(shí),它會(huì)自動(dòng)在主線程創(chuàng)建一個(gè)Looper對(duì)象,用于處理Handler中的message。Looper實(shí)現(xiàn)了簡單的消息隊(duì)列,在循環(huán)中一個(gè)接一個(gè)的處理Message對(duì)象。大多數(shù)Application框架事件(比如Activity生命周期調(diào)用,按鈕點(diǎn)擊等)都在Message中,它們?cè)贚ooper的消息隊(duì)列中一個(gè)接一個(gè)的處理。注意Looper是存在于application整個(gè)生命周期中。
- 當(dāng)你新建了一個(gè)handler對(duì)象后,它會(huì)被分配給Looper的消息隊(duì)列。被發(fā)送到消息隊(duì)列的Message將保持對(duì)Handler的引用,因?yàn)楫?dāng)消息隊(duì)列處理到這個(gè)消息時(shí),需要使用[Handler#handleMessage(Message)](http://developer.android.com/reference/android/os/Handler.html#handleMessage(android.os.Message)這個(gè)方法。(也就是說,只要沒有處理到這個(gè)Message,Handler就一直在隊(duì)列中被引用)
- 在java中,非靜態(tài)的內(nèi)部Class與匿名Class對(duì)它們外部的Class有強(qiáng)引用。static inner class除外。
引用關(guān)系
現(xiàn)在,我們嘗試運(yùn)行如下代碼
- 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();
- }
- }
這個(gè)程序很簡單,我們可以腦補(bǔ)一下,它應(yīng)該是啟動(dòng)了又瞬間關(guān)閉,但是事實(shí)真的是關(guān)閉了嗎?
稍有常識(shí)的人可以看出,它發(fā)送了一個(gè)Message,將在十分鐘后運(yùn)行,也就是說Message將被保持引用達(dá)到10分鐘,這就照成了至少10分鐘的內(nèi)存泄露。
最后正確的代碼如下
- 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();
- }
- }
結(jié)論
- GC是按照有向圖是否可達(dá)來判斷對(duì)象實(shí)例是否有用
- 如果不在需要某個(gè)實(shí)例,卻仍然被引用,這個(gè)情況叫做內(nèi)存泄露
- 匿名類/非靜態(tài)類內(nèi)部class會(huì)保持對(duì)它所在Activity的引用,使用時(shí)要注意它們的生命周期不能超過Activity,否則要用static inner class
- 善于在Activy中的生命周期(比如onPause)中手動(dòng)控制其他類的生命周期
最后再補(bǔ)充一下iOS的情況,iOS在新版的OC與Swift中,已經(jīng)引入了新的內(nèi)存管理體系A(chǔ)RC(auto reference counting,引用自動(dòng)計(jì)數(shù)),C代碼在編譯時(shí),編譯器自動(dòng)適時(shí)的添加釋放內(nèi)存的代碼。
References
http://www.jianshu.com/p/22e73e80e950
http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/
http://stackoverflow.com/a/70358/4016014
http://blog.csdn.net/luoshengyang/article/details/8852432
http://developer.android.com/training/articles/perf-tips.html
https://techblog.badoo.com/blog/2014/08/28/android-handler-memory-leaks/