使用記憶體分析器檢查應用程式's 記憶體用量

記憶體分析器是 Android Profiler 中的元件,可協助您找出可能導致延遲、沒有回應、甚至應用程式異常終止的記憶體流失情形。這項工具會顯示應用程式記憶體用量的即時圖表,並可讓您擷取記憶體快照資料、強制收集垃圾,以及追蹤記憶體配置。

如要開啟記憶體分析器,請按照下列步驟操作:

  1. 依序按一下「View」>「Tool Windows」>「Profiler」 (也可以點選工具列中的「Profile」圖示 )。
  2. 選取要從 Android 分析的裝置和應用程式程序 Profiler 工具列如果已透過 USB 連接裝置,但找不到該裝置 列出必備項目 已啟用 USB 偵錯功能
  3. 在「MEMORY」時間軸上的任一處按一下,即可開啟記憶體分析器。

或者,您也可以在指令列中使用以下項目,檢查應用程式記憶體: dumpsys 等 請參閱 Logcat 中的 GC 事件

為什麼您必須分析應用程式記憶體

Android 提供 代管記憶體環境— 判斷您的應用程式已不再使用部分物件, 將未使用的記憶體釋出回堆積。我們正持續改善 Android 尋找未使用記憶體的運作機制,不過在所有 Android 版本中,系統有時會暫停執行您的程式碼。在多數情況下,這類暫停情形不易察覺。不過,如果應用程式配置記憶體的速度勝過於系統收集記憶體的速度,即使收集器釋出足以滿足配置需求的記憶體,應用程式仍可能會發生延遲情形。這類延遲可能會導致應用程式略過畫格,並讓顯示畫面變慢。

即使應用程式執行速度沒有變慢,但如果發生記憶體流失情形,應用程式在背景中運作時仍會保留該記憶體。這個行為可能會拖慢其餘時間 強制系統進行不必要的垃圾收集 事件。系統最後會強制終止應用程式程序來回收記憶體。之後,當使用者返回應用程式時,就必須完全重新啟動。

為避免發生這類問題,建議您使用記憶體分析器執行下列操作:

  • 在時間軸中尋找不理想且可能會導致效能問題的記憶體配置模式。
  • 產生 Java 記憶體快照資料,查看哪些物件正在占用記憶體。您可以在長時間內產生多個記憶體快照資料,用來識別記憶體流失情形。
  • 記錄正常與極端使用者互動期間的記憶體配置 指出程式碼在何處配置過多物件 或分配因外洩而外洩的物件

進一步瞭解可減少應用程式記憶體的程式設計做法 請參閱「管理應用程式的記憶體」。

記憶體分析器總覽

首次開啟記憶體分析器時,您會看到 應用程式的記憶體用量和存取工具,強制進行垃圾收集、擷取堆積 並記錄記憶體配置情形

圖 1. 記憶體分析器

如圖 1 所示,記憶體分析器的預設檢視畫麵包含 包括:

  1. 強制執行垃圾收集事件的按鈕。
  2. 擷取記憶體快照資料的按鈕。

    附註:記錄記憶體的按鈕 記憶體配置只有在發生以下情況時,才會出現在記憶體快照資料按鈕右側 並連線至搭載 Android 7.1 (API 級別 25) 以下版本的裝置。

  3. 指定分析器擷取記憶體配置的頻率的下拉式選單。選取適當選項有助於提升分析作業期間的應用程式效能
  4. 縮放時間軸的按鈕。
  5. 跳轉至即時記憶體資料的按鈕。
  6. 顯示活動狀態、使用者輸入內容事件以及螢幕旋轉事件的事件時間軸。
  7. 記憶體用量時間軸,包括下列項目:
    • 堆疊長條圖,顯示每個記憶體類別目前的記憶體用量,以左側 y 軸和頂端的色彩索引鍵表示。
    • 虛線,顯示已配置的物件數量,以右側 y 軸表示。
    • 代表每個垃圾收集事件的圖示。

不過,如果您使用的裝置搭載 Android 7.1 以下版本,就無法使用所有裝置 系統預設會顯示剖析資料。如果看到「進階」訊息 所選程序無法使用剖析功能。」你需要 啟用進階剖析功能 以瞭解下列資訊:

  • 事件時間軸
  • 已配置的物件數量
  • 垃圾收集事件

在 Android 8.0 以上版本中,系統會一律啟用進階分析功能,以便進行偵錯 應用程式。

記憶體的計算方式

記憶體分析器頂端顯示的數字 (圖 2) 是根據 Android 系統中應用程式提交的所有專用記憶體頁面。這個總數不包含與系統或其他應用程式共用的頁面。

圖 2. 記憶體分析器頂端的記憶體總數圖例

記憶體總數可分為以下類別:

  • Java:這類記憶體來自透過 Java 或 Kotlin 程式碼配置的物件。
  • Native:這類記憶體來自透過 C 或 C++ 程式碼配置的物件。

    即使您並未在應用程式中使用 C++,仍可能會看到一些原生記憶體 因為 Android 架構使用原生記憶體 例如處理圖片素材資源 。

  • Graphics:讓圖像緩衝區佇列在螢幕上顯示像素 (包括 GL 表面、GL 材質等) 時所用的記憶體 (請注意,這類記憶體會與 CPU 共用,並不是專屬 GPU 記憶體)。

  • Stack:應用程式中由原生堆疊和 Java 堆疊使用的記憶體。這通常與應用程式執行的執行緒數量有關。

  • Code:應用程式用於程式碼和資源 (例如 dex) 的記憶體 位元碼、最佳化或編譯的 dex 程式碼、so 程式庫和字型。

  • Others:由應用程式使用但系統不確定如何分類的記憶體。

  • Allocated:由應用程式配置的 Java/Kotlin 物件數量。這不會計入 C 或 C++ 中配置的物件。

    如果連接至搭載 Android 7.1 以下版本的裝置,只有在記憶體分析器連線至您執行中的應用程式時,系統才會開始計算記憶體配置量。因此,在您開始進行分析前所配置的任何物件都不會列入計算。然而,Android 8.0 以上版本包含裝置端分析工具,可追蹤所有的記憶體配置量,因此這個數字一律代表應用程式在 Android 8.0 以上版本中執行時產生的 Java 物件總數。

與舊有 Android Monitor 工具的記憶體總數相比,新的記憶體分析器記錄記憶體的方式有所不同,因此您的記憶體用量現在看起來比較高。記憶體分析器會監控一些額外的類別 但如果您只在意 Java 堆積記憶體 「Java」數字應該與前一個工具的值相似。 雖然 Java 數字可能與 Android 中顯示的次數不一致 監視器,新數字代表了之前的所有實體記憶體頁面 。這個 能準確反映應用程式的實際記憶體大小 實際應用方式

查看記憶體配置

記憶體配置會顯示記憶體中每個 Java 物件和 JNI 參照的配置方式。具體來說,記憶體分析器會顯示下列物件配置相關資訊:

  • 配置的物件類型及其記憶體用量。
  • 每項配置的堆疊追蹤,包括其執行緒所在位置。
  • 物件遭「取消配置」的時間 (僅適用於搭載 Android 8.0 的裝置) 或更高版本。

如要記錄 Java 和 Kotlin 配置,請依序選取「Record Java/Kotlin allocations」>「Record」。如果裝置搭載 Android 8 以上版本,記憶體分析器 UI 會轉換至另一個畫面,顯示進行中的記錄情形。您可以與記錄上方的迷你時間軸互動,例如變更選取範圍。如要完成記錄作業,請選取「Stop」圖示

記憶體分析器中以視覺化方式呈現 Java 配置

如果裝置搭載 Android 7.1 以下版本,記憶體分析器會使用舊版配置記錄功能,在時間軸上顯示記錄情形,直到您點選「Stop」

選取時間軸的特定區域 (或錄製完畢後) 執行 Android 7.1 以下版本的裝置工作階段時,分配到的 就會依類別名稱分組顯示物件 堆積數量。

如要檢查配置記錄,請按照下列步驟操作:

  1. 瀏覽清單,找出有異常大量堆積總數且可能流失的物件。如要找出已知課程,請按一下課程名稱 欄標題就能按照字母順序排序。然後點選類別名稱。視窗右側會出現「Instance View」窗格,顯示該類別的所有執行個體 (如圖 3 所示)。
    • 或者,您也可以按一下「篩選器」,快速找到物件 , 或按下 Control+F 鍵 (在 Mac 上為 Command+F 鍵),然後輸入類別或套件 名稱。或選取方法名稱,即可搜尋 在下拉式選單中選取「Arrange by callstack」。如果想用 運算式,請勾選「Regex」旁邊的核取方塊。勾選旁邊的方塊 比對大小寫
  2. 在「Instance View」窗格中,點選某個執行個體。「Call Stack」分頁 如下所示,顯示執行個體的配置位置以及位於哪個執行緒中。
  3. 在「Call Stack」分頁中的任一行,按一下滑鼠右鍵並選取「Jump to Source」,即可在編輯器中開啟對應的程式碼。

圖 3. 右側的「Instance View」會顯示每個已配置物件的詳細資料

已配置物件的清單上方有兩個選單,可用來選擇要檢查的堆積以及資料整理方式。

在左側選單中選擇要檢查的堆積:

  • default heap:當系統未指定堆積時。
  • image heap:系統開機映像檔,包含開機期間已預先載入的類別。此處的配置保證絕不會移動或消失。
  • zygote heap:Android 系統中寫入時複製的堆積,應用程式的程序是從這個堆積分支而來。
  • app heap:主要堆積,應用程式會在這個堆積上配置記憶體。
  • JNI heap:顯示 Java Native Interface (JNI) 位置的堆積 參考檔案也會進行分配和發布

在右側選單中選擇配置的安排方式:

  • Arrange by class:根據類別名稱將所有配置分組 (此為預設值)。
  • Arrange by package:根據套件名稱將所有配置分組。
  • Arrange by callstack:將所有配置分成對應的呼叫堆疊群組。

提升分析作業期間的應用程式效能

為了提升分析作業期間的應用程式效能,記憶體分析器預設會定期對記憶體配置進行取樣。在執行 API 的裝置上測試時 26 或以上等級,只要變更這個行為, 使用「Allocation Tracking」下拉式選單。可用選項如下:

  • Full:擷取記憶體中的所有物件配置。這是 Android Studio 3.2 以下版本的預設行為。如果您的應用程式 配置大量物件時,您可能會發現效能明顯遲緩 分析。
  • Sampled:定期對記憶體中的物件配置進行取樣。這是預設行為,而且在分析作業期間對應用程式效能的影響較小。如果應用程式在短時間內配置大量物件,仍可能會發生效能明顯遲緩的情形。
  • Off:停止追蹤應用程式的記憶體配置。

查看全域 JNI 參照

Java Native Interface (JNI) 是一種允許 Java 程式碼和原生程式碼互相呼叫的架構。

JNI 參照是透過原生程式碼手動管理,因此對於原生程式碼使用的 Java 物件,系統的保留時間可能會過長。如果未先明確刪除就捨棄 JNI 參照,Java 堆積上的部分物件可能會無法存取。此外,您也可能會用盡全域 JNI 參照上限。

如要排解這類問題,請使用記憶體分析器中的「JNI Heap」檢視畫面瀏覽所有的全域 JNI 參照,並依照 Java 類型和原生呼叫堆疊進行篩選。只要透過這項資訊,即可找出全域 JNI 參照的建立位置和刪除位置。

在應用程式執行期間,選取您要檢查的時間軸部分,然後從類別清單上方的下拉式選單中選取「JNI heap」。接著您可以照常檢查堆積中的物件,並按兩下 物件前往「Allocation Call Stack」分頁查看 JNI 參照的位置 並在程式碼中發布及釋出,如圖 4 所示。

圖 4. 檢視全域 JNI 參照

如要檢查應用程式 JNI 程式碼的記憶體配置,必須將應用程式部署至搭載 Android 8.0 以上版本的裝置。

如要進一步瞭解 JNI,請參閱「JNI 提示」。

原生記憶體分析器

Android Studio 記憶體分析器提供應用程式的原生記憶體分析器 並部署至搭載 Android 10 以上版本的實體和虛擬裝置。

原生記憶體分析器會追蹤特定時間範圍內原生程式碼中的物件配置和取消配置情形,並提供下列資訊:

  • Allocations:透過 malloc()new 配置的物件數量 運算子。
  • Deallocations:在所選時間範圍內,透過 free()delete 運算子取消配置的物件數量。
  • Allocations Size:在此期間所有配置的匯總大小 (以位元組為單位) 選定的時間範圍
  • Deallocations Size:所有已釋出記憶體的匯總大小 (以位元組為單位) 在指定時間範圍內
  • 總數:「Allocations」欄中的值減去 「Deallocations」欄。
  • Remaining Size:「Allocations size」資料欄中的值減去 「Deallocations size」資料欄中的值。

原生記憶體分析器

如要針對搭載 Android 10 以上版本的裝置記錄原生配置情形,請依序選取「Record native allocations」>「Record」。錄音 直到您點選「Stop」為止 , 然後記憶體分析器 UI 轉換至另一個畫面 原生錄製內容

記錄原生配置按鈕

如果是搭載 Android 9 以下版本的裝置,則無法使用「Record native allocations」選項。

根據預設,原生記憶體分析器使用的樣本大小為 32 位元組;意即每配置 32 個位元組的記憶體時,系統就會建立記憶體快照。樣本大小越小,快照擷取頻率就越高,進而產生更準確的記憶體用量資料。樣本大小越大,資料準確度就越低,但消耗的系統資源較少,並且能提升分析作業期間的效能。

如何變更原生記憶體分析器的樣本大小:

  1. 依序選取「Run」>「Edit Configurations」
  2. 在左側窗格中選取您的應用程式模組。
  3. 點選「Profiling」分頁標籤,然後在標示為 原生記憶體取樣間隔 (位元組)
  4. 再次建構並執行應用程式。

擷取記憶體快照資料

記憶體快照資料會顯示在您擷取記憶體快照資料時,應用程式中有哪些物件正在使用記憶體。尤其是在長時間使用者工作階段之後,記憶體快照資料可顯示您認為不應位於記憶體中、但仍位於記憶體中的物件,藉此找出記憶體流失情形。

擷取記憶體快照資料後,您可以查看下列資料:

  • 應用程式已配置的物件類型及各類數量。
  • 每個物件的記憶體用量。
  • 在程式碼中保留物件參照的位置。
  • 物件配置位置的呼叫堆疊 (目前,在記錄配置期間,只有透過 Android 7.1 以下版本擷取記憶體快照資料時,才能使用呼叫堆疊)。

如要擷取記憶體快照資料,請按一下「Capture heap dump」,然後選取「Record」。擷取記憶體快照資料時,Java 記憶體的數量可能會暫時增加。這是正常現象,因為記憶體快照資料會在與應用程式相同的程序中發生 因此需要一些記憶體來收集資料

分析器完成擷取記憶體快照資料後,記憶體分析器 UI 會轉換至另一個顯示記憶體快照資料的畫面。

圖 5. 查看記憶體快照資料。

如要仔細瞭解快照資料的建立時間,您可以呼叫 dumpHprofData() 在應用程式程式碼的重要位置上建立記憶體快照資料。

您可以在類別清單中查看下列資訊:

  • Allocations:堆積中的配置數量。
  • Native Size:該物件類型使用的原生記憶體總量 (以位元組為單位)。只有 Android 7.0 以上版本才會顯示這個資料欄。

    這裡會顯示 Java 中已配置的部分物件,因為 Android 會針對某些架構類別 (例如 Bitmap) 使用原生記憶體。

  • Shallow Size:該物件類型使用的 Java 記憶體總量 (以位元組為單位)。

  • Retained Size:該類別的所有執行個體所保留的記憶體總大小 (以位元組為單位)。

已配置物件的清單上方有兩個選單,可用來選擇要檢查的記憶體快照資料以及資料整理方式。

在左側選單中選擇要檢查的堆積:

  • default heap:當系統未指定堆積時。
  • app heap:主要堆積,應用程式會在這個堆積上配置記憶體。
  • image heap:系統開機映像檔,包含開機期間已預先載入的類別。此處的配置保證絕不會移動或消失。
  • zygote heap:Android 系統中寫入時複製的堆積,應用程式的程序是從這個堆積分支而來。

在右側選單中選擇配置的安排方式:

  • Arrange by class:根據類別名稱將所有配置分組 (此為預設值)。
  • Arrange by package:根據套件名稱將所有配置分組。
  • Arrange by callstack:將所有配置分成對應的呼叫堆疊群組。只有在記錄配置期間的記憶體快照資料時,才能使用這個選項。即使如此,堆積中也可能有在您開始記錄前已配置的物件,因此系統會優先顯示這些配置 (依類別名稱列出)。

根據預設,清單會依「Retained Size」欄排序。依 值,按一下該資料欄的標題。

按一下類別名稱即可開啟右側的「Instance View」視窗 (如圖 6 所示)。所列的每種執行個體都包含下列項目:

  • Depth:從任何 GC 根目錄到所選執行個體的最短躍點。
  • Native Size:原生記憶體中該執行個體的大小。只有 Android 7.0 以上版本才會顯示這個資料欄。
  • Shallow Size:Java 記憶體中該執行個體的大小。
  • Retained Size:該執行個體支配的記憶體大小 (請參閱「支配樹狀圖」)。

圖 6. 時間軸會顯示擷取記憶體快照資料所需時間

如要檢查堆積,請按照以下步驟操作:

  1. 瀏覽清單,找出有異常大量堆積總數且可能流失的物件。如要找出已知課程,請按一下課程名稱 欄標題就能按照字母順序排序。然後點選類別名稱。視窗右側會出現「Instance View」窗格,顯示該類別的所有執行個體 (如圖 6 所示)。
    • 或者,您也可以按一下「篩選器」,快速找到物件 , 或按下 Control+F 鍵 (在 Mac 上為 Command+F 鍵),然後輸入類別或套件 名稱。或選取方法名稱,即可搜尋 在下拉式選單中選取「Arrange by callstack」。如果想用 運算式,請勾選「Regex」旁邊的核取方塊。勾選旁邊的方塊 比對大小寫
  2. 在「Instance View」窗格中,點選某個執行個體。「References」分頁標籤下方會顯示該物件的所有參照。

    或者,按一下執行個體名稱旁邊的箭頭即可查看所有欄位,接著按一下欄位名稱 即可查看所有參照如要查看某個欄位的例項詳細資料,請在欄位上按一下滑鼠右鍵並選取「Go to Instance」

  3. 在「References」分頁中,如果您發現某個參照可能會導致記憶體流失,請在該參照上按一下滑鼠右鍵,並選取「Go to Instance」。這項操作會從記憶體快照資料中選取對應的執行個體,並顯示該快照的執行個體資料。

在記憶體快照資料中,請注意下列任一原因所導致的記憶體流失情形:

  • 長時間參照 ActivityContextViewDrawable 以及可能會保留 ActivityContext 容器參照的物件。
  • 可保留 Activity 執行個體的非靜態內部類別 (例如 Runnable)。
  • 保留物件超過必要時間的快取。

將記憶體快照資料儲存為 HPROF 檔案

擷取記憶體快照資料後,只有在記憶體分析器執行期間才能查看分析器中的資料。一旦結束分析工作階段,就會遺失記憶體快照資料。因此,如果您要儲存資料以供日後查看,請匯出記憶體快照資料 匯出成 HPROF 檔案在 Android Studio 3.1 以下版本中,「ExportCapture to file」 按鈕位於時間軸下方的工具列左側;英吋 Android Studio 3.2 以上版本會顯示「Export Heap Dump」按鈕 。在隨即顯示的「Export As」對話方塊中,使用 .hprof 副檔名儲存檔案。

如要使用其他 HPROF 分析工具,例如 jhat、 您必須將 HPROF 檔案從 Android 格式轉換為 Java SE HPROF 格式。 您可以使用 android_sdk/platform-tools/ 目錄中的 hprof-conv 工具完成這項操作。接著執行含有兩個引數 (原始 HPROF 檔案和已轉換 HPROF 檔案寫入位置) 的 hprof-conv 指令,範例如下:

hprof-conv heap-original.hprof heap-converted.hprof

匯入記憶體快照資料檔案

如要匯入 HPROF (.hprof) 檔案,請按一下「Start a new profiling session」 是 「工作階段」窗格,選取「從檔案載入」,然後從檔案中選擇檔案 。

您也可以將 HPROF 檔案從檔案瀏覽器拖曳至 編輯器視窗。

記憶體分析器中的記憶體流失偵測功能

在記憶體分析器中分析記憶體快照資料時,可篩選 Android Studio 認為可能代表應用程式中的 ActivityFragment 執行個體導致記憶體流失的分析資料。

這項篩選條件會顯示的資料類型包括:

  • 已遭刪除但仍受參照的 Activity 執行個體。
  • 不含有效 FragmentManager 但仍受參照的 Fragment 執行個體。

在某些情況下,這項篩選條件可能會產生誤判結果,例如:

  • Fragment 已完成建立但未曾使用。
  • Fragment 已被快取,但不屬於 FragmentTransaction 的一部分。

如要使用這項功能,請先擷取記憶體快照資料,或將記憶體快照資料檔案匯入Android Studio。如要顯示可能導致記憶體流失的片段和活動,請在記憶體分析器的記憶體快照資料窗格中,勾選「Activity/Fragment Leaks」核取方塊 (如圖 7 所示)。

分析器:記憶體流失情形偵測功能

圖 7. 篩選記憶體快照資料,掌握記憶體流失情形。

分析記憶體的技巧

使用記憶體分析器時,請對應用程式程式碼施加壓力,並嘗試強制發生記憶體流失問題。如要在應用程式中引發記憶體流失,其中一個方法就是在檢查堆積前先讓應用程式運作一段時間。記憶體流失情形可能會逐漸蔓延至堆積中的配置頂部。不過,流失情形越少 您需要拉長 執行應用程式以便查看。

您也可以透過下列任一方式觸發記憶體流失:

  • 在不同的活動狀態下,將裝置從直向轉為橫向再轉回直向,依此多次反覆執行。旋轉裝置時,經常會導致應用程式發生流失 Activity, Context,或 View 物件,因為系統 會重新建立 Activity 應用程式擁有對其中一個物件的參照,系統無法垃圾收集。
  • 在不同的活動狀態下,在您的應用程式與其他應用程式之間切換 (前往主畫面,然後返回應用程式)。

提示:您也可以使用 monkeyRunner 測試架構來執行上述步驟。