mat---Memory Monitor檢測內存泄露

上篇說了一些性能優化的理論部分,主要是回顧一下,有了理論,小平同志又講了,實踐是檢驗真理的唯一標準,對於內存泄露的問題,現在通過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
  • 圖中水平方向是時間軸,豎直方向是內存的分配情況
  • 圖中深藍色的區域,表示當前正在使用中的內存總量,淺藍色或者淺灰色區域,表示空閒內存或者叫作未分配內存。
  • 左上角工具欄三個圓圈按鈕依次代表
    GC按鈕 ,可以手動GC,回收程序垃圾
    內存快照(Dump Java Heap) ,點擊可以生成一個文件(包名+日期+“.hprof”),可以記錄摸一個時間點內,程序內存的情況
    Allocation Traking ,點擊一次開始, 再次點擊結束,也可以可以生成一個文件。

回到我們的程序,多點擊幾次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

"
在這個界面中可以直接把內存泄露可能的類找出來。

下面分析一下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

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



作者:LooperJing
鏈接:http://www.jianshu.com/p/ef9081050f5c
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
發佈了88 篇原創文章 · 獲贊 18 · 訪問量 76萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章