綜述
記憶體洩漏(memory leak)是指由於疏忽或錯誤造成程式未能釋放已經不再使用的記憶體。那麼在Android中,當一個物件持有Activity的引用,如果該物件不能被系統回收,那麼當這個Activity不再使用時,這個Activity也不會被系統回收,那這麼以來便出現了記憶體洩漏的情況。在應用中內出現一次兩次的記憶體洩漏獲取不會出現什麼影響,但是在應用長時間使用以後,若是存在大量的Activity無法被GC回收的話,最終會導致OOM的出現。那麼我們在這就來分析一下導致記憶體洩漏的常見因素並且如何去檢測記憶體洩漏。
導致記憶體洩漏的常見因素
情景一:靜態Activity和View
靜態變數Activity和View會導致記憶體洩漏,在下面這段程式碼中對Activity的Context和TextView設定為靜態物件,從而產生記憶體洩漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private static Context context; private static TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; textView = new TextView(this); } } |
情景二:Thread,匿名類,內部類
在下面這段程式碼中存在一個非靜態的匿名類物件Thread,會隱式持有一個外部類的引用LeakActivity,從而導致記憶體洩漏。同理,若是這個Thread作為LeakActivity的內部類而不是匿名內部類,他同樣會持有外部類的引用而導致記憶體洩漏。在這裡只需要將為Thread匿名類定義成靜態的內部類即可(靜態的內部類不會持有外部類的一個隱式引用)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class LeakActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); leakFun(); } private void leakFun(){ new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } |
情景三:動畫
在屬性動畫中有一類無限迴圈動畫,如果在Activity中播放這類動畫並且在onDestroy中去停止動畫,那麼這個動畫將會一直播放下去,這時候Activity會被View所持有,從而導致Activity無法被釋放。解決此類問題則是需要早Activity中onDestroy去去呼叫objectAnimator.cancel()來停止動畫。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class LeakActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); textView = (TextView)findViewById(R.id.text_view); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360); objectAnimator.setRepeatCount(ValueAnimator.INFINITE); objectAnimator.start(); } } |
情景四:Handler
對於Handler的記憶體洩漏在(Android的訊息機制——Handler的工作過程)[http://blog.csdn.net/ljd2038/article/details/50889754]這篇文章中已經詳細介紹,就不在贅述。
情景五:第三方庫使用不當
對於EventBus,RxJava等一些第三開源框架的使用,若是在Activity銷燬之前沒有進行解除訂閱將會導致記憶體洩漏。
使用MAT檢測記憶體洩漏
對於常見的記憶體洩露進行介紹完以後,在這裡再看一下使用MAT(Memory Analysis Tool)來檢測記憶體洩露。MAT的下載地址為:http://www.eclipse.org/mat/downloads.php。
下面來看一段會導致記憶體洩露的錯誤程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class LeakActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); EventBus.getDefault().register(this); } @Subscribe public void subscriber(String s){ } } |
在上面這段程式碼中有會導致記憶體洩漏,原因是EventBus沒有解除註冊。下面就以這段程式碼為例來看一下如何分析記憶體洩漏。
開啟AndroidStudio中的Monitors可以看到如下介面。
在這裡可以看到在應用剛啟動的時候,所佔用的記憶體為15M,然後我們現在開始操作APP,反覆進入退出LeakActicity。點選上如中的GC按鈕。這時候我們在看一下記憶體使用情況。
在這裡我們可以看到,記憶體一直在持續增加,已經達到33M,並且無法被GC所回收。所以我們可以判斷,這時候必然出現記憶體洩漏的情形。那麼現在再點選Dump Java Heap按鈕,在captures視窗看到生成得hprof檔案。但這時候所生成的hprof檔案不是標準格式的,我們需要通過SDK所提供的工具hprof-conv進行轉化,該工具在SDK的platform-tools目錄下。執行命令如下:
1 |
hprof-conv XXX.hprof converted-dump.hprof |
當然在AndroidStudio中可以省去這一步,可以直接匯出標準格式的hprof檔案。
這時候可以通過MAT工具來開啟匯出的hprof檔案。開啟介面如下圖所示:
在MAT中我們最常用的就是Histogram和Dominator Tree,他們分別對應上圖中的A和B按鈕。Histogram可以看出記憶體中不同型別的buffer的數量和佔用記憶體的大小,而Dominator Tree則是把記憶體中的物件按照從大到小的順序進行排序,並且可以分析物件之間的引用關係。在這裡再來介紹一下MAT中兩個符號的含義。
- ShallowHeap:物件自身佔用的記憶體大小,不包括他引用的物件
- RetainedHeap:物件自身佔用的記憶體大小並且加上它直接或者間接引用物件的大小
Histogram
由於在Android中一般記憶體洩漏大多出現在Acivity中,這時候可以點選Histogram按鈕,並搜尋
Activity。
在這裡可以看出LeakActivity存在69個物件,基本上可以斷定存在記憶體洩漏的情形,這時候便可以通過檢視GC物件的引用鏈來進行分析。點選滑鼠右鍵選擇Merge Shortest paths to GC Roots並選擇exclude weak/soft references來排除弱引用和軟引用。
在排除軟引用和弱引用以後如下圖所示:
在這裡可以看出由於EventBus導致的LeakActivity記憶體洩漏。
在Histogram中還可以檢視一個物件包含了那些物件的引用。例如,現在要檢視LeakActivity所包含的引用,可以點選滑鼠右鍵,選擇list objects中的with incoming reference。而with outcoming reference表示選中物件持有那些物件的引用。
Dominator Tree
現在我們點選這時候可以點選Dominator Tree按鈕,並搜尋
Activity。可以看到如下圖所示:
在這裡可以看到存在大量的LeakActivity。然後點選滑鼠右鍵選擇Path To GC Roots->exclude weak/soft references來排除弱引用和軟引用。
之後可以看到如下結果,依然是EventBus導致的記憶體洩漏:
總結
記憶體洩漏往往被我們所忽略,但是當大量的記憶體洩漏以後導致OOM。它所造成的影響也是不容小覷的。當然除了上述記憶體洩漏的分析以為我們還可以通過LeakCanary來分析記憶體洩漏。對於LeakCanary的使用在這裡就不在進行詳細介紹。