1. 程式人生 > Android開發 >使用 Android Studio Profiler 工具解析應用的記憶體和 CPU 使用資料

使用 Android Studio Profiler 工具解析應用的記憶體和 CPU 使用資料

為了幫助開發者開發出更加輕快高效的應用,我們在 Android Studio 3.0 以及更高版本中加入了 Android Profiler 工具,用於應用的 CPU、記憶體、網路和能耗分析。

在 Android Profiler 提供的這四種效能資料中,絕大多數場景下我們都更關心 CPU 和記憶體的使用情況。本文將介紹對應的兩種分析工具 —— Memory Profiler 和 CPU Profiler。

Memory Profiler

許多開發者使用 Memory Profiler,是希望發現和定位記憶體洩漏問題。在介紹 Memory Profile 如何解決這一問題之前,我想先明確 "記憶體洩漏" 這一概念。無論您當前是否瞭解記憶體洩漏,都將幫助我更好地解釋 Memory Profile 的工作原理。

記憶體洩漏

什麼是記憶體洩漏?

通常我們認為,在執行的程式中,如果一個無法訪問的物件卻仍然佔用著記憶體空間,即為此物件造成了記憶體洩漏。如果您使用過 C 語言或 C++ 的指標,您會很熟悉這個概念。

但是在 Kotlin 和 Java 的世界中,事情有些許不同。因為這兩種語言是執行在 Java 虛擬機器器 (JVM) 中的。在 JVM 中,有個重要的概念,就是垃圾回收 (GC)。當垃圾回收執行時,虛擬機器器會首先識別 GC Root。GC Root 是一個可以從堆外部訪問的物件,它可以是本地變數或執行中的執行緒等。虛擬機器器會識別所有可以從 GC Root 訪問的物件,它們將會被保留。而其他無法從 GC root 訪問的物件,則會被認為是垃圾並回收掉。

所以,一般意義上的記憶體洩漏在 JVM 中並不存在。在 JVM 中的記憶體洩漏通常是指: 記憶體中含有那些再也不會被使用、但是仍然能夠訪問的物件。

Activity 和 Fragment 洩漏檢測

在 Android 應用中,應當尤為警惕 Activity 和 Fragment 物件的洩漏,因為這兩種物件通常都會佔用很多記憶體。在 Android 3.6 中,Memory Profiler 加入了自動檢查 Activity 和 Fragment 中的記憶體洩漏的功能。使用這一功能非常的簡單:

  • 首先,您需要在 Memory Profiler 中儲存 Heap Dump,點選下圖所示按鈕:

  • 在 Heap Dump 載入完成後,勾選 "Activity/Fragment Leaks" 選框:

此時如果有檢查到 Activity 或 Fragment 的洩漏,就會在介面中顯示出來。

Memory Profiler 通過以下幾種場景來判斷洩漏是否發生:

  • 當我們銷燬了一個 Activity 的例項後,這個例項就再也不會被使用了。此時如果仍然有這個 Activity 的引用,Memory Profiler 就會認為它已經洩漏;
  • Fragment 的例項應當與一個 Fragment Manager 相關聯,如果我們看到一個 Fragment 沒有關聯任何一個 Fragment Manager,而且它依然被引用時,也可以認為有洩漏發生。

不過要注意的是,針對 Fragment 有個特別的情況: 如果您載入的 Heap Dump 的時機,剛好介於 Fragment 被建立和被使用的時間之間,就會造成 Memory Profiler 誤報;相同情況也會發生在 Fragment 被快取但是沒有被複用的時候。

其他記憶體洩漏檢測

Memory Profiler 也可以用於檢查其他型別的洩漏,它提供了許多資訊,用於幫助您識別記憶體洩漏是否發生。

當您拿到一段 Heap Dump 之後,Memory Profiler 會展示出類的列表。對於每個類,"Allocation" 這一列顯示的是它的例項數量。而在它右邊則依次是 "Native Size"、"Shallow Size" 和 "Retained Size":

這幾組資料分別意味著什麼呢?下面我會通過一個例子來說明。

我們用下圖來表示某段 Heap Dump 記錄的應用記憶體狀態。注意紅色的節點,在這個示例中,這個節點所代表的物件從我們的工程中引用了 Native 物件:

這種情況不太常見,但在 Android 8.0 之後,使用 Bitmap 便可能產生此類情景,因為 Bitmap 會把畫素資訊儲存在原生記憶體中來減少 JVM 的記憶體壓力。

先從 "Shallow Size" 講起,這列資料其實非常簡單,就是物件本身消耗的記憶體大小,在上圖中,即為紅色節點自身所佔記憶體。

而 "Native Size" 同樣也很簡單,它是類物件所引用的 Native 物件 (藍色節點) 所消耗的記憶體大小:

"Retained Size" 稍複雜些,它是下圖中所有橙色節點的大小:

由於一旦刪除紅色節點,其餘的橙色節點都將無法被訪問,這時候它們就會被 GC 回收掉。從這個角度上講,它們是被紅色節點所持有的,因此被命名為 "Retained Size"。

還有一個前面沒有提到的資料維度。當您點選某個類名,介面中會顯示這個類例項列表,這裡有一列新資料 —— "Depth":

"Depth" 是從 GC Root 到達這個例項的最短路徑,圖中的這些數字就是每個物件的深度 (Depth):

一個物件離 GC Root 越近,它就越有可能與 GC Root 有多條路徑相連,也就越可能在垃圾回收中被儲存下來。

以紅色節點為例,如果從其左邊來的任何一個引用被破壞,紅色節點就會變成不可訪問的狀態並且被垃圾回收回收掉。而對於右邊的藍色節點來說,如果您希望它被垃圾回收,那您需要把左右兩邊的路徑都破壞才行。

值得警惕的是,如果您看到某個例項的 "Depth" 為 1 的話,這意味著它直接被 GC root 引用,同時也意味著它永遠不會被自動回收。

下面是一個示例 Activity,它實現了 LocationListener 介面,高亮部分程式碼 "requestLocationUpdates" 將會使用當前 Activity 例項來註冊 locationManager。如果您忘記登出,這個 Activity 就會洩漏。它將永遠都待在記憶體裡,因為位置管理器是一個 GC root,而且永遠都存在:

您能在 Memory Profiler 中檢視這一情況。點選一個例項,Memory Profiler 將會開啟一個面板來顯示誰正在引用這個例項:

我們可以看到位置管理器中的 mListener 正在引用這個 Activity。您可以更進一步,通過引用面板導航至堆的引用檢視,它可以讓您驗證這條引用鏈是否是您所預期的,也能幫您理解程式碼中是否有洩漏以及哪裡有洩漏。

CPU Profiler

和 Memory Profiler 類似,CPU Profiler 提供了從另一個角度記錄和分析應用關鍵效能資料的方法。

使用 CPU Profiler,首先要產生一些 CPU 的使用記錄:

  • 進入 Android Studio 中的 CPU Profiler 介面,在您的應用已經部署的前提下,點選 "Record" 按鈕;
  • 在應用中進行您想要分析的操作;
  • 返回 CPU Profiler,點選 "Stop" 按鈕。

由於最終呈現的資料是基於執行緒組織的,所以去觀察資料之前,您應該確認是否選擇了正確的執行緒:

我們這裡所獲得的 CPU 使用記錄資訊,其實是一個 System Trace 例項的呼叫棧集合 (下文統稱 "呼叫棧")。而就算是很短的 CPU 使用記錄,也會包含巨量的資訊,同時這些資訊也是人無法讀懂的。所以 CPU Profiler 提供了一些工具來視覺化這些資料。

Call Chart

在 CPU Profiler 介面下半部,有四個標籤頁,分別對應四個不同的資料圖表,它們分別是: Call Chart、Flame Chart、Top Down 和 Bottom Up。其中的 Call Chart 可能是最直白的一個,它基本上就是一個呼叫棧的重新組織和視覺化呈現:

Call Chart 橫軸就是時間線,用來展示方法開始與結束的確切時間,縱軸則自上而下展示了方法間呼叫和被呼叫的關係。Call Chart 已經比原資料可讀性高很多,但它仍然不方便發現那些執行時間很長的程式碼,這時我們便需要使用 Flame Chart。

Flame Chart

Flame Chart 提供了一個呼叫棧的聚合資訊。與 Call Chart 不同的是,它的橫軸顯示的是百分比數值。由於忽略了時間線資訊,Flame Chart 可以展示每次呼叫消耗時間佔用整個記錄時長的百分比。同時縱軸也被對調了,在頂部展示的是被呼叫者,底部展示的是呼叫者。此時的圖表看起來越往上越窄,就好像火焰一樣,因此得名:

Flame Chart 是基於 Call Chart 來重新組織資訊的。從 Call Chat 開始,合併相同的呼叫棧,以耗時由長至短對呼叫棧進行排序,就獲得了 Flame Chart:

對比兩種圖表不難看出,左邊的 Call Chart 有詳細的時間資訊,可以展示每次呼叫是何時發生的;右邊的 Flame Chart 所展示的聚合資訊,則有助於發現一個總耗時很長的呼叫路徑:

Top Down Tree

前面介紹的兩種圖表,可以幫助我們從兩種角度縱覽全域性。而如果我們需要更精確的時間資訊,就需要使用 Top Down Tree。在 CPU Profiler 中,Top Down 選項卡展示的是一個資料表格,為了便於理解其中各組資料的意義,接下來我們會嘗試構建一個 Top Down Tree。

構建一個 Top Down Tree 並不複雜。以 Flame Chart 為基礎,您只需要從呼叫者開始,持續新增被呼叫者作為子節點,直到整個 Flame Chart 被遍歷一遍,您就獲得了一個 Top Down Tree:

對於每個節點,我們關注三個時間資訊:

  • Self Time —— 執行自己的程式碼所消耗的時間;
  • Children Time —— 呼叫其他方法的時間;
  • Total Time —— 前面兩者時間之和。

有了 Top Down Tree,我們能輕易將這三組資訊歸納到一個表格之中:

下面我們來看一看這些時間資訊是怎麼計算的。左手邊是和前面一樣的 Flame Chart 示例。右邊則是一個 Top Down Tree。

我們從 A 節點開始:

  • A 消耗了 1 秒鐘來執行自己的程式碼,所以 Self Time 是 1;
  • 然後它消耗了 9 秒中去呼叫其他方法,這意味著它的 Children Time 是 9;
  • 這樣就一共消耗了 10 秒鐘,Total Time 是 10;
  • B 和 D 以此類推...

值得注意的是,D 節點只是呼叫了 C,自己沒做任何事,這種情況在方法封裝時很常見。所以 D 的 Children Time 和 Total Time 都是 2。

下面是表格完全展開的狀態。當您在 Android Studio 中分析應用時,CPU Profiler 會完成上面所有的計算,您只要理解這些數字是怎麼產生的即可:

對比左右兩邊: Flame Chart 比較便於發現總耗時很長的呼叫鏈,而 Top Down Tree 則方便觀察其中每一步所消耗的精確時間。作為一個表格,Top Down Tree 也支援按單獨維度進行排序,這點同樣非常實用。

Bottom Up Tree

當您希望方便地找到某個方法的呼叫棧時,Bottom Up Tree 就派上用場了。"樹" 如其名,Bottom Up Tree 從底部開始構建,這樣我們就能通過在節點上不斷新增呼叫者來反向構建出樹。由於每個獨立節點都可以構建出一棵樹,所以這裡其實是森林 (Forest):

讓我們再做些計算來搞定這些時間資訊。

表格有四行,因為我們有四個樹在森林中。從節點 C 開始:

  • Self Time 是 4 + 2 = 6 秒鐘;
  • C 沒有呼叫其他方法,所以 Children Time 是 0;
  • 前面兩者相加,總時間為 6 秒鐘。

看起來與 Top Bottom Tree 別無二致。接下來展開 C 節點,計算 C 的呼叫者 B 和 D 的情況。

在計算 B 和 D 節點的相關時間時,情況與前面的 Top Bottom Tree 有所不同:

  • 由於我們在構建基於 C 節點的 Bottom Up Tree,所以所有時間資訊也都是基於 C 節點的。這時我們在計算 B 的 Self Time 時,應當計算 C 被 B 呼叫的時間,而不是 B 自身執行的時間,這裡是 4 秒;對於 D 來說,則是 2 秒。
  • 由於只有 B 和 D 呼叫 C 的方法,它們的 Total Time 之和應與 C 的 Total Time 相等。

下一個樹是 B 節點的 Bottom Up Tree,它的 Self Time 是 3 秒,Children Time 是用來呼叫其他方法的時間,這裡只有 C,所以是 2 秒。Total Time 永遠都是前兩者之和。下面便是整個表格展開的樣子:

當您想要觀察某個方法如何被呼叫,比如這個 nanoTime() 方法時,您可以使用 Bottom Up Tree 並觀察 nanoTime 方法的子節點列表,通過右邊的時間資料,您可以找到那個您所感興趣的呼叫:

備忘表

前面介紹了四種不同的資料圖表,並且還詳細解釋了一些資料是如何被計算出來的。如果您覺得頭緒太多很難記住,沒關係,下面這個簡明的備忘表就是為您準備的:

總結

本文介紹了 Android Studio Profiler 中的兩種資料分析工具。

其中 Memory Profiler 可以自動檢測 Activity 和 Fragment 的記憶體洩漏,而通過瞭解和使用 Memory Profiler 中資料分析功能提供的資料,也可以發現和解決其他型別的記憶體洩漏問題。

有關 CPU Profiler 則介紹了 Call Chart、Flame Chart、Top Down、Bottom Up 這四種維度的資料呈現。

希望這些內容能夠幫助您更加了解 Android Profiler。如仍有疑問,歡迎在下方留言。也歡迎通過 Android Studio 反饋使用中遇到的問題。

您也可以通過視訊回顧 2019 Android 開發者峰會演講 —— 讀懂 Android Studio 分析工具資料:

視訊連線:v.qq.com/x/page/m302…

點選這裡即刻閱讀更多應用效能相關內容