1. 程式人生 > >Android內存優化1 內存檢測工具1 Memory Monitor檢測內存泄露

Android內存優化1 內存檢測工具1 Memory Monitor檢測內存泄露

pri 二次 多個 內存泄漏 可選 分配內存 blog android .net

上篇說了一些性能優化的理論部分,主要是回顧一下,有了理論,小平同誌又講了,實踐是檢驗真理的唯一標準,對於內存泄露的問題,現在通過Android Studio自帶工具Memory Monitor 檢測出來。性能優化的重要性不需要在強調,但是要強調一下,我並不是一個老司機,嘿嘿!沒用過這個工具的,請睜大眼睛。如果你用過,那麽就不用在看這篇博客了。

先看一段會發生內存泄露的代碼

public class UserManger {

    private static UserManger instance;

    private Context context;

    private UserManger(Context context) {
        this.context = context;
    }

    public static UserManger getInstance(Context context) {
        if (instance == null) {
            instance = new UserManger(context);
        }
        return instance;
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        UserManger userManger = UserManger.getInstance(this);
    }
}

代碼很簡單,就是一個單利模式泄露的場景,我們現在的關心的不是代碼本身,而是如何將代碼裏面的內存泄露給找出來。但是對於上面的代碼發生內存泄露的原因還是有必要提一下。

上篇博客說了,內存泄漏產生的原因是:當一個對象已經不需要再使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用從而就導致,對象不能被回收。這種導致了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏。

在上面的代碼中,發生泄露的不是UserManger,而是MainActivity,UserManger中有一個靜態成員instance,其生命周期和應用程序的生命周期一致,當退出應用時,才能被銷毀,但是當GC準備回收MainActivity時,結果呢MainActivity的對象(this)在被UserManger所引用,UserManger本身又不能被幹掉,所以就發生了內存泄露。

技術分享圖片
monitors.png


Memory Monitor是Android Monitors中的一種,Monitors主要包括四種,Memory Monitor ,CPU Monitor ,NetWork Monitor, GPU Monitor ,今天介紹的是Memory Monitor ,其他的Monitor,在後面也準備講。

  • Memory Monitor界面
技術分享圖片
Memory Monitor.png
  • 圖中水平方向是時間軸,豎直方向是內存的分配情況
  • 圖中深藍色的區域,表示當前正在使用中的內存總量,淺藍色或者淺灰色區域,表示空閑內存或者叫作未分配內存。
  • 內存分析的工具欄,從上向下一共4個按鈕,依次是:
    1. 技術分享圖片 終止檢測的開關,沒什麽實質性的作用

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

      技術分享圖片 (Dump Java Heap)點擊可以生成一個文件(包名+日期+“.hprof”),可以記錄摸一個時間點內,程序內存的情況。獲取hprof文件(hprof文件是我們使用MAT工具分析內存時使用的文件),但這裏直接產生的文件MAT還不能直接使用,需用轉換成標準的hprof文件。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到

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

回到我們的程序,多點擊幾次GC,看一下這個應用的內存使用情況。

技術分享圖片
內存使用情況.jpg

可以看到現在已經分配的內存有19.68M,我把手機旋轉一下,在看。

技術分享圖片
旋轉後內存使用情況.png

可以看到現在的內存使用量是21.09M,還是一樣的界面,卻多了1.41M!!!這很關鍵。

接下來,我們找一下,哪裏發生了泄露。點擊Dump Java Heap,生成快照文件tool.test.memory.memoryleak_2016.11.13_21.38.hprof,Android Studio 自動彈出HPROF Viewer來分析它。

技術分享圖片
快照文件分析.png

現在介紹一下HPROF Viewer的用法

  • HPROF Viewer查看方式
    左上角兩個紅框,是可選列表, 分別是用來選擇Heap區域, 和Class View的展示方式的.
    Heap類型分為:
    App Heap -- 當前App使用的Heap
    Image Heap -- 磁盤上當前App的內存映射拷貝
    Zygote Heap -- Zygote進程Heap(每個App進程都是從Zygote孵化出來的, 這部分基本是framework中的通用的類的Heap)
    Class List View -- 類列表方式
    Package Tree View -- 根據包結構的樹狀顯示

我通常點擊App heap下面的Classs Name把Heap中所有類按照字母順序排序,然後按照字母順序查找。

  • HPROF Viewer主要分ABC三大板塊
    板塊A:這個應用中所有類的名字
    版塊B:左邊類的所有實例
    板塊C:在選擇B中的實例後,這個實例的引用樹
  • A板塊左上角列名解釋
列名解釋
Class Name 類名,Heap中的所有Class
Total Count 內存中該類這個對象總共的數量,有的在棧中,有的在堆中
Heap Count 堆內存中這個類 對象的個數
Sizeof 每個該實例占用的內存大小
Shallow Size 所有該類的實例占用的內存大小
Retained Size 所有該類對象被釋放掉,會釋放多少內存
  • B板塊右上角上角列名解釋
列名解釋
Instance 該類的實例
Depth 深度, 從任一GC Root點到該實例的最短跳數
Dominating Size 該實例可支配的內存大小

B板塊右上角有個"的按鈕, 點擊會進入HPROF Analyzer的hprof的分析界面:

技術分享圖片
Analyzer Tasks.png

點擊Analyzer Tasks右邊的綠色運行箭頭,Android Studio會自動的根據此hprof文件分析有哪些類是有內存泄漏的,如下圖所示:


下面分析一下MainActivity的泄露情況
技術分享圖片
MainActivity發生內存泄露.png 在這個界面中可以直接把內存泄露可能的類找出來。
  • 一個Activity應該只有一個實例,但是從A區域來看 total count的值為2,heap count的值也為2,說明有一個是多余的。
  • 在B區域中可以看見兩個MainActivity的實例,點擊一個看他的引用樹情況
  • 在C區域中可以看到MainActivity的實例Context被UserManger的 instance引用了,引用深度為1.
  • 在Analyzer Tasks 區域中,直接告訴你Leaked Activities,MainActivity包含其中

多方面的證據表明MainActivity發生了內存泄露

解決方案

public class UserManger {

    private static UserManger instance;

    private Context context;

    private UserManger(Context context) {
        this.context = context;
    }

    public static UserManger getInstance(Context context) {
        if (instance == null) {
            if(context!=null){
                instance = new UserManger(context.getApplicationContext());
            }
        }
        return instance;
    }
}

不要用Activity的Context,因為Activity隨時可能被回收,我們用Application的Context,Application的Context的生命周期是整個應用,不回收也沒有關系。

Memory Monitor獲得內存的動態視圖,Heap Viewer顯示堆內存中存儲了什麽,可惜Heap Viewer不能顯示你的數據具體分配在代碼的何處,如果還不過癮,想知道具體是哪些代碼使用了內存,還有一個功能是Allocation Tracker,用來內存分配追蹤。在內存圖中點擊途中標紅的部分,啟動追蹤,再次點擊就是停止追蹤,隨後自動生成一個alloc結尾的文件,這個文件就記錄了這次追蹤到的所有數據,然後會在右上角打開一個數據面板
Allocation Tracker啟動追蹤

技術分享圖片
Allocation Tracker啟動追蹤.png

Allocation Tracker查看方式

技術分享圖片
Allocation Tracker查看方式

有兩種查看方式,默認是Group by Method方式

  • Group by Method:用方法來分類我們的內存分配
  • Group by Allocator:用內存分配器來分類我們的內存分配

從上圖可以看出,首先以線程對象分類,Size是內存大小,Count是分配了多少次內存,點擊一下線程就會查看每個線程裏所有分配內存的方法

  • Group by Method方式

    技術分享圖片
    每個線程裏所有分配內存的方法.png


    OK,-Memory Monitor

  • Group by Allocator方式

    技術分享圖片
    EY%HY_B74%BUE22C6$G~CTP.png


    右鍵可以直接跳到源碼

- 扇形統計圖

技術分享圖片
AQFHT$@7TYP0S_1`DU@%S%6.png

點擊統計圖按鈕,會生成上圖,扇形統計圖是以圓心為起點,最外層是其內存實際分配的對象,每一個同心圓可能被分割成多個部分,代表了其不同的子孫,每一個同心圓代表他的一個後代,每個分割的部分代表了某一帶人有多人,你雙擊某個同心圓中某個分割的部分,會變成以你點擊的那一代為圓心再向外展開。

Memory Monitor可以發現的問題

Memory Monitor工具為監控工具,是一種發現型或者說監控性質的工具,比如醫生的四大技能[望聞問切],[望]是第一步。這裏的Memory Monitor就是一種[望]的工具,目前我主要用它來看下面幾個內存問題:
1.發現內存抖動的場景
2.發現大內存對象分配的場景
3.發現內存不斷增長的場景
4.確定卡頓問題是否因為執行了GC操作

案例分析

技術分享圖片

上面的第一段標記顯示內存突然增加了7M,我們也能看的很清楚,所以這個點我們要去定位了一下問題在哪裏,是Bitmap還是什麽原因造成的,第二段標記是內存抖動,很明顯在很短的時間了發生了多次的內存分配和釋放。而且在發生內存抖動的時候,也能感覺到App的卡頓,可以看出來是由於執行了GC操作造成的。
內存的不斷增加通過Memory monitor很容易看出來,藍色的曲線是一路高歌猛進的,一看便知。

Android內存優化1 內存檢測工具1 Memory Monitor檢測內存泄露