Android內存問題及調試工具

一. 關於內存的幾個概念

1. GC 的工做機制

當 GC 工做時,虛擬機中止其餘工做。頻繁地觸發 GC 進行內存回收,會致使系統性能嚴重降低。php

2. 內存抖動

在極短的時間內,分配大量的內存,而後又釋放它,這種現象就會形成內存抖動。典型地,在 View 控件的 onDraw 方法裏分配大量內存,又釋放大量內存,這種作法極易引發內存抖動,從而致使性能降低。由於 onDraw 裏的大量內存分配和釋放會給系統堆空間形成壓力,觸發 GC 工做去釋放更多可用內存,而 GC 工做起來時,又會吃掉寶貴的幀時間 (幀時間是 16ms) ,最終致使性能問題。html

3. 內存泄露

Java 語言的內存泄漏概念和 C/C++ 不太同樣,在 Java 裏是指不正確地引用致使某個對象沒法被 GC 釋放,從而致使可用內存愈來愈少。好比,一個圖片查看程序,使用一個靜態 Map 實例來緩存解碼出來的 Bitmap 實例來加快加載進度。這個時候就可能存在內存泄漏。java

內存泄漏會致使可用內存愈來愈少,從而致使頻繁觸發 GC 回收內存,進而致使性能降低。android

二. 調試工具

  • Memory Monitor: 能夠查閱 GC 被觸發起來的時間序列,以便觀察 GC 是否影響性能。
  • Allocation Tracker: 從 Android Studio 的這個工具裏查看一個函數調用棧裏,是否有大量的相同類型的 Object 被分配和釋放。若是有,則其可能引發性能問題。
  • MAT: 這是 Eclipse 的一個插件,也有單獨的工具能夠下載使用。

三. 兩個簡單的實例

1. 內存抖動

經過一個很是簡單的例子來演示內存抖動。這個例子裏,在自定義 View 的 onDraw 方法裏大量分配內存來演示內存抖動和性能之間的關係。canvas

版本一:緩存

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        String msg = "";
        for (int i = 0; i < 500; i++) {
            if (i != 0) {
                msg += ", ";
            }
            msg += Integer.toString(i + 1);
        }
        Log.d("DEBUG", msg);
    }

版本二:性能優化

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 500; i ++) {
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(i + 1);
        }
        Log.d("DEBUG", sb.toString());
    }

內存抖動的特徵:從 Memory Monitor 來看,有毛刺出現。即短期內分配大量的內存並觸發 GC。oracle


              memory_churn

從 Allocation Tracker 裏看,一次操做會有大量的內存分配產生。app


              memory_tracker

2. 內存泄漏

這個例子裏,咱們簡單地讓點擊 Settings 菜單,就產生一個 100KB 的內存泄漏。dom

private void addSomeCache() {
        // add 100KB cache
        int key = new Random().nextInt(100);
        Log.d("sfox", "add cache for key " + key);
        sCache.put(key, new byte[102400]);
    }

內存泄漏的特徵:從 Memory Monitor 來看,內存佔用愈來愈大


              memory_tracker

利用 MAT 工具進行專業分析。這是個很大的話題。幾乎能夠獨立成幾個章節來說。能夠參閱 MAT 自己自帶的 Tutorials 來學習。另外,這篇文章裏的分析方法是個不錯的開始。

四. 利用 MAT 分析內存問題

一個典型的問題是 Android 系統越用越慢。這種典型地是由內存泄漏引發的。一個頗有用的解決這種問題的辦法是:比較先後兩個階段的內存的使用狀況。通常流程以下:

  • 利用 ddms 工具 dump HPROF file
  • 利用 hprof-conv 把 dalvik 格式的轉換爲普通 jvm 格式
  • 重複步驟 1 和 2 抓出兩份 LOG。
  • 利用 MAT 對兩份 HRPOF 文件進行分析,結合代碼找出可能存在的內存泄漏

好比針對撥號盤愈來愈慢的問題,咱們能夠開機後啓動撥號盤,打進打出10個電話。而後抓個 HPROF 文件。接着,再打進打出10個電話,再抓一個 HPROF 文件。接着拿這兩個文件對比分析,看是否是會形成電話打進打出越多,內存佔用越多的狀況發生。

HPROF文件:HPROF 簡單地理解,就是從 jvm 裏 dump 出來的內存和 CPU 使用狀況的一個二進制文件。它的英文全名叫 A Heap/CPU Profiling Tool。這裏有它完整的官方文檔和它的歷史介紹。

打開 MAT 後,會有一個 Tutorials 來教你們怎麼用。這裏列出幾個操做步驟及其注意事項。

  • 在 DDMS 裏導出 HPROF 文件前,最好手動執行一下 GC。目的是讓導出的內存所有是被引用的。不然在作內存佔用對比時,會有不少沒必要要的內存佔用被標識出來,干擾咱們進行分析。
  • 進行對比時,最好是選擇操做較多的和操做較少的對比,這樣得出的 delta 是正數
  • 經過對比,發現內存泄漏時,能夠用 OQL 來查詢,並經過 Root to GC 功能來找到發生泄漏的源代碼

在咱們的示例程序裏面,每次點擊 Settings 菜單,都會致使一次100KB的內存泄漏。下面是咱們利用上面介紹的流程來查找內存泄漏問題。咱們先點擊 5 次 Settings 菜單,而後手動觸發一次 GC,再導出 HPROF 文件。接着,咱們再點擊 6 次 Settings 菜單,而後手動觸發一次 GC,再導出第二份 HPROF 文件。咱們拿這兩份 HPROF 就能夠作一些對比。

             mat_diff.png

經過上圖能夠看到,兩次操做確實致使了某些類的實例增長了。圖中能夠清楚地看到 byte[] 和 java.util.HashMap$HashMapEntry 兩個類增長得比較明顯。這樣,咱們隨便選擇一個,經過 OQL 來查詢系統中的這個內存。

             mat_qql.png

從上圖能夠找到,本次 dump 出來的內存裏,確實有不少個這個類的實例。在圖上右擊任何一個實例,右擊,選擇 Paths to GC roots ,能夠找到這個實例是被誰引用的。

              mat_gc_root.png

從上圖能夠看出來,這個內存是被 MainActivity 裏的 sCache 引用的。經過閱讀代碼,咱們就能夠找到這個漏洞了。即每次都往 sCache 裏保存一個引用。

參考資料:

  1. Android 性能優化內存篇
  2. Android性能優化典範胡凱的博客
相關文章
相關標籤/搜索