一、前言
我們知道,Android系統檢測到app有不再使用對象時,就會進行記憶體回收相關的工作。
盡管Android檢測無用對象、回收記憶體的方法在不斷改進,
但在目前所有的Android版本中,進行上述工作時,系統仍需要短暫地停止app的運作。
在大多數情況下,系統進行記憶體回收的行為是無法被使用者察覺到的。
然而,如果應用配置設定記憶體的速度大于系統回收的速度,
那麼app程序的正常運作可能就回受到影響。
畢竟,系統必須回收到足夠的供app需要的記憶體,才會恢複處于暫停狀态的app。
在這種情況下,app就可能出現掉幀、卡頓等現象。
在更嚴重的情況下,如果出現了記憶體洩露的問題,那麼系統中就可能堆積無法釋放的記憶體,
使得系統必須更加頻繁地進行記憶體回收,進而降低系統的性能。
甚至在極端條件下,系統不得不殺死部分正在背景運作的app程序。
于是使用者将背景應用移到前台時,卻發現應用無故重新開機,這顯然帶來了較差的使用者體驗。
由此可見,記憶體對于app而言,是極其關鍵的性能名額。
目前,分析app記憶體的工具有很多,
本文主要記錄一下Android Studio内置的記憶體分析工具Memory Profiler。
二、基本介紹
Memory Profiler是Android Profiler的一個元件, 用于幫助分析記憶體洩露和記憶體抖動的問題。
當PC連接配接Android L以上的裝置時,該工具才能夠正常使用。
Memory Profiler的功能包括:
展示應用記憶體使用情況的實時圖像、抓取記憶體的dump資訊、強制垃圾回收及追蹤記憶體配置設定。
2.1 開啟步驟
打開Memory Profiler的步驟為:
1、 依次點選Android Studio的View → Tool Windows → Android Profiler,
或直接點選工具欄Android Profiler對應的圖示;
2、 PC連接配接Android終端後,在Android Profiler對應的區域選擇接的裝置和需要監控的程序:

3、 點選Android Profiler界面中MEMORY區域的任意位置,即可開啟Memory Profiler,如下圖所示:
需要注意的是,如果PC連接配接Android 7.1以下的裝置時,有些關鍵資料可能無法被Android Profiler統計,
此時Android Profiler會顯示如下資訊:
這時我們需要依次點選Android Studio的Run → Edit Configurations → Profiling 按鍵,選中app後點選Enabled advanced profiling,
如下圖所示:
為了支援該功能,要求app對應的gradle版本必須在2.4以上。
2.2 界面介紹
打開Memory Profiler後,主界面如下所示(為了友善,這裡直接盜取Android技術文檔中的圖):
其中:
标注1對應的按鍵用于強制記憶體回收。
标注2對應的按鍵用于抓取程序記憶體的dump資訊。
标注3對應的按鍵用于記錄記憶體的配置設定資訊(連接配接Android 7.1及以下才會有此按鍵)。
初次點選時,對應統計的開始時間點;再次點選時,對應統計的結束時間點。
程序在兩個時間點之間的記憶體配置設定資訊,将被Memory Profiler記錄和分析。
标注4對應的區域用于縮放時間軸。
标注5對應的按鍵用于顯示實時的記憶體資料。
标注6對應的區域用于記錄事件發生的時間點及大緻持續的時間(例如activity狀态改變、使用者操作界面等事件)。
标注7對應的區域用于顯示記憶體使用情況對應的時間軸(與标注6結合,就可以看出各事件帶來的記憶體變化情況)。
需要說明的是,标注7對應區域顯示的内容包括:
不同類型記憶體占用情況對應的圖像;
配置設定對象數量對應的短畫線;
記憶體回收事件發生的時機。
2.3 統計的資料類型及含義
Memory Profiler主要根據Android系統提供的資訊,
統計app獨自占用記憶體,即不統計app與系統或其它app共有的記憶體。
Memory Profiler統計記憶體的種類如下圖所示:
如上圖所示,其中:
Java表示Java代碼或Kotlin代碼配置設定的記憶體;
Native表示C或C++代碼配置設定的記憶體(即使App沒有native層,調用framework代碼時,也有可能觸發配置設定native記憶體);
Graphics表示圖像相關緩存隊列占用的記憶體;
Stack表示native和java占用的棧記憶體;
Code表示代碼、資源檔案、庫檔案等占用的記憶體;
Others表示無法明确分類的記憶體;
Allocated表示Java或Kotlin配置設定對象的數量(Android8.0以下時,僅統計Memory Profiler啟動後,程序再配置設定的對象數量;
8.0以上時,由于系統内置了統計工具,Memory Profiler可以得到整個app啟動後配置設定對象的數量)。
三、基本用法
對Memory Profiler有了基本的了解後,我們來看看它的基本用法。
3.1 檢視記憶體配置設定情況
Memory Profiler可以檢視兩個時間點之間的記憶體配置設定情況,包括:
對象的類型、占用記憶體的大小、棧資訊等。
連接配接8.0以上的裝置時,Memory Profiler還可以顯示對象被回收的時間。
PC連接配接8.0以上的裝置時,在記憶體統計的時間線上,直接點選和拖動就可以選擇觀察區域;
連接配接低版本的裝置時,則需要點選Record Memory allocations按鍵(2.2小結介紹的标注3)選擇觀察區域。
標明觀察區域後, Memory Profiler就可以統計這段時間内app配置設定記憶體的情況:
從圖中可以看出,Memory Profiler可以顯示配置設定對象的類名;
點選類後,會在Instance View顯示具體的對象;
點選具體對象後,會在Call back區域顯示調用棧。
點選調用棧資訊後,就會跳轉到具體的代碼。
3.2 檢視記憶體占用情況
點選2.2小結介紹的标注2,即可抓取點選後一段時間内app占用記憶體的dump資訊。
通過dump資訊,我們可以看到app目前仍存在于記憶體中的對象。
結合代碼,我們可以分析是否有本應被析構卻仍存活的洩露對象。
與統計記憶體配置設定資訊一樣,記憶體占用資訊同樣會顯示對象的類型、數量、占用記憶體的大小、引用關系等。
如下圖所示:
圖中Alloc Count表示堆中配置設定對象的數量;
Shallow Size表示對象使用Java記憶體的大小,機關為byte;
Retained Size表示對象占用的實際記憶體大小,大于等于Shallow Size;
7.0及以上版本的裝置,還會顯示對象占用的Native Size。
點選具體的對象時,也會顯示Instance View。
此時,Instance View顯示的資訊變多了,包括:
Depth表示目前對象到任一GC root的最短跳數;
Shallow Size、Retained Size的含義與前文一緻。
同樣,7.0及以上版本的裝置,還會顯示對象占用的Native Size。
從圖中可以看出,Instance View不會顯示棧資訊。
如果想獲得棧資訊的話,必須先點選Record Memory allocations按鍵。
四、使用示例
利用Memory Profiler,我分析了一下某反病毒引擎SDK的記憶體占用情況。
随便寫了個demo,繼承SDK後啟動app,記憶體占用情況如下圖所示:
然後,通過操作UI初始化SDK,發現穩定後記憶體占用情況如下圖所示:
比對前後兩圖,不考慮界面UI變化消耗的記憶體,
可以看出:記憶體增加的主體部分來自于Native函數,其它類型的記憶體變化幾乎可以忽略。
這是因為該SDK初始化時,最主要的工作是在Native層加載底層的so檔案。
接下來,我們在demo裡調用SDK的接口,批量掃描樣本,統計記憶體消耗情況如下圖所示:
從圖中可以看出app消耗的記憶體飙升到了104M,
與初始化後的記憶體相比,其中增加主要是code和native類型的記憶體。
根據2.3小結的描述,我們知道code類型統計的是app程序需要的資源檔案、庫檔案等,
是以這部分記憶體主要是SDK中引擎加載病毒庫等消耗掉的記憶體;
而Native記憶體的消耗,應該也是由于引擎掃描檔案導緻的。
按照3.1小結的描述,我們檢視了一下批量掃描這段時間内,
app程序記憶體配置設定的情況,如下圖所示:
容易看出,在這段時間内,除去native記憶體外,整個app配置設定記憶體并不大,
且按照對象占用記憶體的大小排序,排在前列的都是基本資料類型。
是以,這段時間内app程序的記憶體應該是比較正常的。
我們進行GC操作,記憶體情況如下圖所示:
發現記憶體幾乎和掃描前一緻,按照3.2小結進行dump分析,沒有發現洩露對象。
是以,可以确認SDK不存在記憶體洩露的問題(dump資訊含有sdk的隐私,這裡就不再附圖了)。
五、總結
至此,我們已經介紹了Android Memory Profiler工具的基本情況,
并簡單列舉了該工具的使用方式。
個人感覺,不同于LeakCanary,
Android Memory Profiler更側重于宏觀場景下的記憶體分析。
版權聲明:轉載請注明:http://blog.csdn.net/gaugamela/article