Android記憶體優化(四)解析Memory Monitor、Allocation Tracker和Heap Dump

劉望舒發表於2019-02-27

相關文章
Android效能優化系列
Java虛擬機器系列

前言

要想做好記憶體優化工作,就要掌握兩大部分的知識,一部分是知道並理解記憶體優化相關的原理,另一部分就是善於運用記憶體分析的工具。本篇就來介紹記憶體分析工具:Memory Monitor、Allocation Tracker和Heap Dump的使用方法。

1.Memory Monitor

在Android Studio(以下簡稱AS)中Android Monitor是一個主視窗,它包含了Logcat,、Memory Monitor、CPU Monitor、 GPU Monitor和Network Monitor。其中Memory Monitor可以輕鬆地監視應用程式的效能和記憶體使用情況,以便於找到被分配的物件,定位記憶體洩漏,並跟蹤連線裝置中正在使用的記憶體數量。Memory Monitor可以報告出你的應用程式的記憶體分配情況, 更形象的呈現出應用程式使用的記憶體。它的作用如下:

  • 實時顯示可用的和分配的Java記憶體的圖表。
  • 實時顯示垃圾收集(GC)事件。
  • 啟動垃圾收集事件。
  • 快速測試應用程式的緩慢是否與過度的垃圾收集事件有關。
  • 快速測試應用程式崩潰是否與記憶體耗盡有關。

1.1 使用Memory Monitor

在使用Memory Monitor之前要確保手機開啟了開發者模式和USB除錯。
使用的步驟為:
1.執行需要監控的應用程式。
2.點選AS皮膚下面的Android圖示,並選擇Monitors選項。
如果Memory Monitor已經執行,效果如下圖所示(AS版本2.3.2)。

圖中的標註的功能如下:

  • Initiate GC(標識1):用來手動觸發GC。
  • Dump Java heap(標識2):儲存記憶體快照。
  • Start/Stop Allocation Tracking(標識3):開啟Allocation Tracker工具(後面會介紹)。
  • Free(標識4):當前應用未分配的記憶體大小。
  • Allocated(標識5):當前應用分配的記憶體大小。

圖中y軸顯示當前應用的分配的記憶體和未分配的記憶體大小;x軸表示經過的時間。

1.2 大記憶體申請與GC

從上圖可以看出,分配的記憶體急劇上升,這就是大記憶體分配的場景,我們要判斷這是否是合理的分配的記憶體,是Bitmap還是其他的大資料,並且對這種大資料進行優化,減少記憶體開銷。
接下來分配的記憶體出現急劇下降,這表示垃圾收集事件,用來釋放記憶體。

1.3 記憶體抖動

記憶體抖動一般指在很短的時間內發生了多次記憶體分配和釋放,嚴重的記憶體抖動還會導致應用程式卡頓。記憶體抖動出現原因主要是短時間頻繁的建立物件(可能在迴圈中建立物件),記憶體為了應對這種情況,也會頻繁的進行GC,因此綜合起來就產生了記憶體抖動,產生了如上圖般的鋸齒狀。

2.Allocation Tracker

Allocation Tracker用來跟蹤記憶體分配,它允許你在執行某些操作的同時監視在何處分配物件,瞭解這些分配使你能夠調整與這些操作相關的方法呼叫,以優化應用程式效能和記憶體使用。
Allocation Tracker能夠做到如下的事情:

  • 顯示程式碼分配物件型別、大小、分配執行緒和堆疊跟蹤的時間和位置。
  • 通過重複的分配/釋放模式幫助識別記憶體變化。
  • 當與 HPROF Viewer結合使用時,可以幫助你跟蹤記憶體洩漏。例如,如果你在堆上看到一個bitmap物件,你可以使用Allocation Tracker來找到其分配的位置。

2.1 使用Allocation Tracker

AS和DDMS中都有Allocation Tracker,這裡會·介紹AS中的Allocation Tracke如何使用。首先要確保要確保手機開啟了開發者模式,並且開啟了USB除錯。
使用的步驟為:
1.執行需要監控的應用程式。
2.點選AS皮膚下面的Android圖示,並選擇Monitors選項。
3.點選Start Allocation Tracking按鈕,這時Start Allocation Tracking按鈕變為了Stop Allocation Tracking按鈕。
4.操作應用程式。
5.點選Stop Allocation Tracking按鈕,結束快照。這時Memory Monitor會顯示出捕獲快照的期間,如下圖所示。

6.過幾秒後就會自動開啟一個視窗,顯示當前生成的alloc檔案的記憶體資料。

2.2 alloc檔案分析

自動開啟的alloc檔案視窗如下圖所示。

該alloc檔案顯示以下資訊:

說明
Method 負責分配的Java方法
Count 分配的例項總數
Total Size 分配記憶體的總位元組數

接著我們來分析標紅框的內容,負責分配的Java方法為performLaunchActivity,記憶體分配序列為2369,分配的物件為ActivityThread,分配的例項總數為300個,分配記憶體的總位元組數為10512。不瞭解performLaunchActivity方法和ActivityThread可以看Android深入四大元件這一系列的文章。

目前的選單選項是Group by Method我們也可以選擇 Group By Allocator,如下圖所示。

為了更好的解釋圖中的資訊,這裡給出測試的程式碼,MainActivity和SecondActivity 的程式碼如下所示。
MainActivity.java

public class MainActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button =(Button)findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,SecondActivity.class));
            }
        });
    }
}複製程式碼

SecondActivity.java

public class SecondActivity extends AppCompatActivity {
    private static Object inner;
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.bt_next);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createInnerClass();
                finish();
            }
        });
    }
    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }
}複製程式碼

其中SecondActivity是存在記憶體洩漏的,生成快照期間,我的操作就是在MainActivity和SecondActivity跳轉了3次(點選button 共6次)。這時我們回過頭來看上圖的紅框的資訊,MainActivity總共分配了3個Intent例項,佔用記憶體為192位元組。SecondActivity總共分配了6個例項,佔用記憶體為96位元組,其中分配了3個匿名內部類OnClickListener的例項,3個InnerClass的例項。

我們可以選擇列表中的一項,單擊滑鼠右鍵,在彈出的選單中選擇jump to the source就可以跳轉到對應的原始檔中。
除此之外,還可以點選Show/Hide Chart按鈕來顯示資料的圖形化,如下圖所示。

3.Heap Dump

Heap Dump的主要功能就是檢視不同的資料型別在記憶體中的使用情況。它可以幫助你找到大物件,也可以通過資料的變化發現記憶體洩漏。

3.1 使用Heap Dump

開啟Android Device Monitor工具,在左邊Devices列表中選擇要檢視的應用程式程式,點選Update Heap按鈕(裝有一半綠色液體的圓柱體),在右邊選擇Heap選項,並點選Cause GC按鈕,就會開始顯示資料。我們每次點選Cause GC按鈕都會強制應用程式進行垃圾回收,並將清理後的資料顯示在Heap工具中。如下圖所示。

從上圖可以看出,Heap工具共有三個區域,分別是總覽檢視(標識1)、詳情檢視(標識2)和記憶體分配柱狀圖(標識2)。

3.2 總覽檢視

其中總覽檢視可以檢視整體的記憶體情況,表中的顯示資訊如下所示。

說明
Heap Size 堆疊分配給該應用程式的記憶體大小
Allocated 已分配使用的記憶體大小
Free 空閒的記憶體大小
%Used 當前Heap的使用率(Allocated/Heap Size)
#Objects 物件的數量

結合上表和上圖,我們在總覽檢視獲得的資訊就是:堆疊分配給當前的應用程式的記憶體大小為2.346MB,已分配的記憶體為1.346MB,空閒的記憶體為1MB,當前Heap的使用率為57.37%,物件的數量為24058個。

3.3 詳情檢視

詳細檢視展示了所有的資料型別的記憶體情況,表中列的資訊如下所示。

說明
Type 資料型別
Total Size 總共佔用的記憶體大小
Smallest 將該資料型別的物件從小到大排列,排在第一個的物件所佔用的記憶體
Largest 將該資料型別的物件從小到大排列,排在最後一個的物件所佔用的記憶體
Median 將該資料型別的物件從小到大排列,排在中間的物件所佔用的記憶體
Average 該資料型別的物件所佔用記憶體的平均值

除了列的資訊,還有行資訊:

說明
free 記憶體碎片
data object 物件
class object
1-byte array (byte[],boolean[]) 1位元組的陣列物件
2-byte array (short[],char[]) 2位元組的陣列物件
4-byte array (object[],int[],float[]) 4位元組的陣列物件
6-byte array (long[],double[]) 8位元組的陣列物件
non-Java object 非Java物件

行資訊中比較重要的是free,它與總覽檢視中的free的含義不同,它代表記憶體碎片。當新建立一個物件時,如果碎片記憶體能容下該物件,則複用碎片記憶體,否則就會從free空間(總覽檢視中的free)重新劃分記憶體給這個新物件。free是判斷記憶體碎片化程度的一個重要的指標。
此外,1-byte array這一行的資訊也很重要,因為圖片是以byte[]的形式儲存在記憶體中的,如果1-byte array一行的資料過大,則需要檢查圖片的記憶體管理了。

3.4 檢測記憶體洩漏

Heap Dump也可以檢測記憶體洩漏。在左邊Devices列表中選擇要檢視的應用程式程式,點選Update Heap按鈕(裝有一半綠色液體的圓柱體),在右邊選擇Heap選項,並點選Cause GC按鈕,就會開始顯示資料,如下圖所示。

這時data object的Total Size為270.266KB。接下來操作應用,這個應用仍舊是在2.2小節所舉的記憶體洩漏的例子,我反覆的在MainActivity和SecondActivity跳轉了10次(點選Button共20次),資料顯示為:

data object的Total Size變為了768.172KB。這時我點選Cause GC按鈕,資料顯示為:

可以看到data object的Total Size變為了444.516KB,再點選一次Cause GC按鈕:

Total Size變為了323.312KB,經過兩次Cause GC的操作,Total Size的值從768.172KB變為了323.312KB,這是一個比較大的變化,說明在Cause GC操作之前有462.86KB(768.172KB-323.312KB)的記憶體沒有被回收,可能發生了記憶體洩漏。

參考資料
Memory Monitor
Allocation Tracker
Android Monitor Basics
Android效能專項測試之Memory Monitor工具
《Android應用效能優化最佳實踐》
《Android群英傳 神兵利器》
《高效能Android應用開發》


我的新書《Android進階之光》已出版,檢視詳情點選這
歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

相關文章