1. 程式人生 > >Android Studio和MAT結合使用來分析記憶體問題

Android Studio和MAT結合使用來分析記憶體問題

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

1

2

3

<code>ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

int memoryClass = am.getMemoryClass();

</code>

上述程式碼中momeryClass的值可以當做每個App的記憶體限制。這個值根據不同的裝置廠商都是不一樣的,比如我的模擬器的值是32M,如果在我的模擬器上執行的一個App,分配的記憶體空間超過32M,則會報OOM(記憶體溢位)!而記憶體洩漏也是一個導致記憶體溢位的隱患,因此必須掌握解決記憶體溢位的方法。

本章主要講解使用Android Studio檢視是否有記憶體洩漏問題,然後使用MAT(Memory Analyzer Tool)來分析並解決記憶體洩漏問題。

Android Studio分析是否有記憶體洩漏

開啟Android Studio中的Android Monitor中的Memory面板,可以看到有一個實時變化的堆記憶體曲線圖,如下圖所示這裡寫圖片描述

上圖中重點列出了3部分內容:<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxjb2RlPrG7suLK1LXE1tW2y8no1sOjrMjnzbzL+cq+ztLKx9TaxKPE4sb3TmV4dXNfU8nP1/ay4srUILG7suLK1LXEvfizzKOsteO797/J0aHU8cbky/tBcHBsaWNhdGlvbrvy1d+9+LPMILWxx7Cxu7LiytS1xL34s8zW0MTatOa31sXkx+m/9jxiciAvPg0KQWxsb2NhdGVktPqx7dLRt9bF5LXEv9W85CBGcmVltPqx7b/J08PKo9Pgv9W85CBBbGxvY2F0ZWQgKyBGcmVlsrvE3LOsQXBwxNq05s/e1sYoMzJNKSA8L2NvZGU+DQo8cD48Y29kZT7E2rTmt9bO9rXEuaS+38C4o6y008nPz/LPwtK7ubI0uPawtMWlo6zSwLTOysejujxiciAvPg0KPGltZyBhbHQ9"Enable" src="/uploadfile/Collfiles/20160811/20160811100327967.png" title="\" /> 終止檢測的開關,沒什麼實質性的作用

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屬性

?

1

2

<code><!--?xml version="1.0" encoding="utf-8"?-->

<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android=><button android:layout_height="wrap_content" android:layout_width="wrap_content" android:onclick="click" android:text="測試Memory Monitor"></button></linearlayout></code>

Activity程式碼如下:宣告Button被點選回撥的click方法

?

1

2

3

4

5

6

<code>public void click(View view) {

for (int i = 0; i < 10000; i++) {

ImageView imageView = new ImageView(this);

list.add(imageView);

}

}</code>

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

<code>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!!!");

}

}</imageview></code>

可以看到,在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的程式碼修改一下

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

<code>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!!!");

}

}

}

</imageview></code>

可以看到,我講造成記憶體洩漏的場景由內部類改成了內部執行緒類,並且線上程中無限迴圈列印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工具

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最終都是被一個叫做MainActivity1的物件引用,而MainActivity1就是在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,這樣就可以進一步定位到具體的程式碼了。