Android Studio和MAT結合使用來分析內存問題


Android開發中時常會遇到內存泄漏的問題,而Android系統對單個App又有一定的內存限制,此值可以通過一下方式獲取:

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();

上述代碼中momeryClass的值可以當做每個App的內存限制。這個值根據不同的設備廠商都是不一樣的,比如我的模擬器的值是32M,如果在我的模擬器上運行的一個App,分配的內存空間超過32M,則會報OOM(內存溢出)!而內存泄漏也是一個導致內存溢出的隱患,因此必須掌握解決內存溢出的方法。

本章主要講解使用Android Studio查看是否有內存泄漏問題,然后使用MAT(Memory Analyzer Tool)來分析並解決內存泄漏問題。

Android Studio分析是否有內存泄漏


打開Android Studio中的Android Monitor中的Memory面板,可以看到有一個實時變化的堆內存曲線圖,如下圖所示
這里寫圖片描述

上圖中重點列出了3部分內容:

  1. 被測試的終端設置,如圖所示我是在模擬器Nexus_S上做測試
  2. 被測試的進程,點擊可選擇其他Application或者進程
  3. 當前被測試的進程中內存分配情況
    • Allocated代表已分配的空間
    • Free代表可用剩余空間
    • Allocated + Free不能超App內存限制(32M)
  4. 內存分析的工具欄,從上向下一共4個按鈕,依次是:
    Enable 終止檢測的開關,沒什么實質性的作用

    InitGC 就是手動調用GC,我們在抓內存前,一定要手動點擊 Initiate GC按鈕手動觸發GC,這樣抓到的內存使用情況就是不包括Unreachable對象的(Unreachable指的是可以被垃圾回收器回收的對象,但是由於沒有GC發生,所以沒有釋放,這時抓的內存使用中的Unreachable就是這些對象)

    DumpHeap 獲取hprof文件(hprof文件是我們使用MAT工具分析內存時使用的文件),但這里直接產生的文件MAT還不能直接使用,需用轉換成標准的hprof文件。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到

    AllocTrack 開始分配追蹤,第一次點擊可以指定追蹤內存的開始位置,第二次點擊可以結束追蹤的位置。這樣我們截取了一段要分析的內存,等待幾秒鍾AndroidStudio會給我們打開一個Allocation視圖(感覺和MAT工具差不多,不過MAT工具更加強大,我們也可以獲取hprof文件,使用MAT來分析)


寫一段代碼動態演示一下:

xml布局文件如下,定義一個Button,並設置onClick屬性

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:text="測試Memory Monitor" android:onClick="click" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> 

通過上面代碼,可以預見,每次點擊Button時,都會動態生成10000個ImageView並添加到List中保存起來,Memory的效果圖如下:
Memory2
可以看到,剛開始系統分配了2M左右的內存,當點擊一次Button之后,內存增加到8M,再次點擊內存增加到24M左右。
上述情況下,當我們按下返回退出Activity時,然后點擊Init GC按鈕執行垃圾回收操作,進程中的內存會重新回到2M,如下圖:
Memory3
這種情況下,代碼是安全穩定的代碼,但是如果Activity中有內存泄漏會是何種情況呢,接下來我們先把之前的代碼修改一下,認為構造一個內存泄漏的場景,如下代碼所示:

public class MainActivity extends AppCompatActivity { private List<ImageView> list = new ArrayList<>(); static MemoryLeak memoryLeak; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (memoryLeak == null) { memoryLeak = new MemoryLeak(); } } public void click(View view) { for (int i = 0; i < 10000; i++) { ImageView imageView = new ImageView(this); list.add(imageView); } } class MemoryLeak { void doSomeThing() { System.out.println("Wheee!!!"); } }

可以看到,在MainActivity中,添加了一個非靜態內存類MemoryLeak,然后聲明了一個靜態MemoryLeak引用。
運行上述代碼,然后再次執行點擊Button的操作,可以看到內存同樣會上升到8M左右,再次點擊上升到16M左右,但是此時按下返回按鈕並執行垃圾回收操作之后,Allocated + Free的總空間並沒有重新回到2M左右,而是一直徘徊於8M左右 說明存在內存泄漏!!! 但是為什么會是8M呢??

Android Studio生成內存字節文件


剛才在介紹Studio的Memory面板時,有提到一個工具欄Dump Java Heap,通過點擊此按鈕就可以導出一個hprof文件,此過程會比較慢,需要耐心等待,當下圖中心的圓圈停止轉動之后hprof文件也就導出成功
這里寫圖片描述

導出完成后將自動打開這個文件,如下圖所示:
hprof
點擊Analyzer Tasks右邊的綠色運行箭頭,Android Studio會自動的根據此hprof文件分析有哪些類是有內存泄漏的,如下圖所示:
mmm
確實有一個MainActivity存在內存泄漏的情況,但是跟我之前預想的有一點出入,本來以為向網上很多人說的那樣,每次打開一個MainActivity時都會造成內存泄漏,但是現在事實就擺在眼前。仔細想了一下也恍然大悟了,MemoryLeak在第一個MainActivity中被聲明是static靜態的,當第二個被打開的MainActivity並不會再重新初始化MemoryLeak對象了,因此static MemoryLeak對象在內存中只是持有了第一個MainActivity的對象的引用,因此當我們調用多次GC操作之后,實際上只有第一個MainActivity不會被GC回收掉!!

如果再將Activity的代碼修改一下

package material.danny_jiang.com.adbmemoryanalyze; import android.app.ActivityManager; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private List<ImageView> list = new ArrayList<>(); //private static MemoryLeak memoryLeak; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //memoryLeak = new MemoryLeak(); } public void click(View view) { for (int i = 0; i < 10000; i++) { ImageView imageView = new ImageView(this); list.add(imageView); } new Thread() { @Override public void run() { super.run(); while (true) { try { System.out.println("Thread running!!"); Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } class MemoryLeak { void doSomeThing() { System.out.println("Wheee!!!"); } } } 

可以看到,我講造成內存泄漏的場景由內部類改成了內部線程類,並且在線程中無限循環打印log。

再次執行進入MainActivity–返回鍵–進入MainActivity–返回鍵的操作

然后再生成hprof文件並打開,並執行Analyzer Tasks,可以看到如下圖片的信息:
yyy
上圖可以看出打開的每一個MainActivity都會造成內存泄漏。 擦嘞!!為什么這會兒又是這種情況呢???這個問題就牽涉到Java中線程的問題了—Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對所有被激活狀態的線程都是持有強引用,導致GC永遠都無法回收掉這些線程對象,除非線程被手動停止並置為null或者用戶直接kill進程操作。看到這相信你應該也是心中有答案了吧 : 我在每一個MainActivity中都創建了一個線程,此線程會持有MainActivity的引用,即使退出Activity當前線程因為是直接被GC Root引用所以不會被回收掉,導致MainActivity也無法被GC回收。所以當使用線程時,一定要考慮在Activity退出時,及時將線程也停止並釋放掉

MAT內存分析工具


正常來講,根據上面我講的使用Studio來分析簡單的內存泄漏已經足夠了,但是在Studio之前有一款更加強大的內存分析工具MAT,谷歌工程師稱它更加的Powerful!!。接下來就看一下如何使用MAT來分析內存問題

1 首先在eclipse官網下載MAT工具

https://www.eclipse.org/mat/

2 下載完MAT並安裝好之后,需要先生成hprof文件。

這兩我還是使用之前線程造成內存泄漏的案例來演示,

  • 首先第一次打開MainActivity時,點擊dump heap生成一個hprof文件
  • 其次進行一系列的操作, 比如點擊Button,按下返回鍵,再次進入MainActivity等等,這里我重復了4遍如上操作,然后再點擊dump heap生成hprof文件

3 點擊Studio的Captures欄,顯示剛才生成的hprof文件,如下圖所示:

two_hprof
這兩個文件我們需要使用MAT去打開並對比分析,但是MAT不能直接打開這兩個文件,需要將它轉換成MAT能夠識別的文件,Captures欄中,右鍵點擊每一個hprof文件,然后選擇Export to standard .hprof並保存到電腦目錄中,如下圖:
export_hprof

4 使用MAT打開轉換后的hprof文件,顯示如下圖

mat
可以看到有兩個dump的面板,其中每一個都顯示了一個內存的餅狀圖。其中用的最多的功能是左下角的Histogram, 點擊 Actions下的 Histogram項將得到 Histogram結果:
histogram
它按類名將所有的實例對象列出來,可以點擊表頭進行排序,在表的第一行可以輸入正則表達式來匹配結果 :
reg
在Histogram中,可以右鍵某一想查看的對象,然后選中List Objects來查看此對象的所有實例,如下圖
histogram_cat

選中之后,會跳出所有實例對象面板,在此面板中可以可以繼續某一特定實例在內存中的Path To GC Root(從GC開始的強引用)。在之前的案例操作中,我重復的進入MainActivity4次,並依次點擊Button運行線程,因此正常來說MainActivity應該有4個實例在內存當中,如下圖

exclude

exclude all phantom/weak/soft的意思是講所有的虛引用/軟引用/弱引用都排除掉,因為只有強引用才會造成內存泄漏!點擊之后顯示下圖信息:

path

可以看到,MainActivity最終都是被一個叫做MainActivity1MainActivity1就是在click方法中創建的匿名內部類Thread對象。 最終我們找到了內存泄漏的根本原因 : 當Activity退出之后,Thread因為被GC Root直接引用,所以不會被GC回收掉,而Thread又持有Activity的引用導致Activity也無法被GC正常回收掉,造成了Activity的內存泄漏,大功告成!!!

如何發現內存泄漏


上面分別介紹了使用Android studio和MAT分析內存的方法。Android studio自帶的內存分析工具直觀方便,但其功能卻不如MAT強大,特別是沒有有效的搜索、排序等功能。遇到一些棘手的問題,可能還是要借助MAT來分析內存。

上面的例子是我們人為制造了一個內存泄漏,然后有意用工具檢測他。但實際開發中,我們如何發現內存泄漏呢?我想可以首先使用studio自帶或DDMS中的heap分析工具,觀察在反復執行某個操作時(例如打開某個頁面、點擊某個按鈕、加載某個資源等等)時,內存在執行GC后能始終維持在穩定的值附近。如果內存呈線性增長的趨勢,那一定是發生了內存泄漏。此時,就要dump出內存鏡像,然后使用工具分析了。

在分析內存時,第一是可以使用工具自帶的泄漏檢查器幫助定位。另外,可以在執行操作(懷疑造成內存泄漏的操作)前后,分別dump出一份內存鏡像,然后使用MAT的Compare Basket對比兩個文件的內存情況,這樣可以幫助定位到是哪個對象發生了泄漏。然后再找到這個對象的GC Roots,這樣就可以進一步定位到具體的代碼了。

常見內存泄漏問題總結

請查看鏈接:http://blog.csdn.net/zxm317122667/article/details/52106741


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM