使用Android Memory Profiler

Memory Profiler是Android Profiler中的一個組件,它能夠幫助您識別內存泄漏和可能致使卡頓、凍結甚至應用程序崩潰的內存抖動。它顯示一個應用程序內存使用的實時圖表,並容許你抓取堆棧信息、強行垃圾收集和跟蹤內存分配。html

要打開Memory Profiler,請執行如下步驟:android

  1. 單擊View > Tool Windows > Profiler(也能夠單擊工具欄中的Profile圖標)。
  2. 從Android Profiler工具欄中選擇要分析的設備和應用程序進程。若是已經過USB鏈接設備,但未看到列出的設備,請確保已啓用USB調試。
  3. 單擊MEMORY時間軸中的任意位置打開Memory Profiler。

爲何要分析應用程序內存

Android提供了一個託管內存環境,當它肯定應用程序再也不使用某些對象時,垃圾收集器會將未使用的內存釋放回堆中。Android尋找未使用內存的方式正在不斷改進,但在全部Android版本中,系統必須短暫暫停代碼。大多數時候,停頓是不被感知的。可是,若是應用程序分配內存的速度快於系統回收內存的速度,則應用程序可能會發生延遲,等待回收器釋放足夠的內存以知足分配。延遲可能致使應用程序發生跳幀並致使明顯變慢。正則表達式

即便你的應用程序並無表現出緩慢,但若是它泄露了內存,即便運行在後臺也能夠佔用內存。此行爲會致使垃圾回收事件被強制執行,從而下降系統的其他內存性能。最後,系統可能被迫終止應用程序進程以回收內存。所以當用戶返回到此應用程序時,它必須徹底從新啓動。api

爲了幫助防止這些問題,您應該經過如下操做使用Memory Profiler進行檢查:瀏覽器

  • 在時間軸中查找可能致使性能問題的不良內存分配模式。
  • 抓取Java堆來查看在任何給定時間哪些對象正在耗盡內存。在一段較長的時間內屢次抓取堆信息有助於識別內存泄漏。
  • 記錄正常和極端的用戶交互過程當中的內存分配,以肯定代碼在短期內分配過多對象的位置或對象泄漏的位置。

Memory Profiler概述

當您第一次打開Memory Profiler時,您將看到應用程序的內存使用的詳細時間軸,和可使用的內存工具包括強制垃圾回收、抓取堆信息和記錄內存分配。緩存

memory-profiler-callouts_2x.png

如上圖所示,Memory Profiler的默認視圖包括如下內容:session

  1. 強制垃圾回收事件的按鈕。
  2. 抓取堆信息的按鈕。注意:僅當鏈接到運行Android7.1(API級別25)或更低版本的設備時,堆信息按鈕右側纔會顯示一個用於記錄內存分配的按鈕。
  3. 用於設置Profiler捕獲內存分配的頻率的下拉菜單。選擇適當的選項能夠幫助您在分析時提升應用程序性能。
  4. 用於放大/縮小時間軸的按鈕。
  5. 一個跳轉到實時內存數據的按鈕。
  6. 事件時間軸,顯示活動狀態、用戶輸入事件和屏幕旋轉事件。
  7. 內存使用時間軸,包括如下內容:app

    • 由每個內存類別使用多少內存的堆疊圖,如左邊的Y軸和頂部的顏色鍵所指示的。
    • 虛線表示分配的對象的數目,如右邊的y軸所示。
    • 每一個垃圾回收事件的圖標。

可是,若是您使用的是運行Android 7.1或更低版本的設備,默認狀況下並不是全部分析數據均可見。若是您看到一條消息,上面寫着「Advanced profiling is unavailable for the selected process」,則須要啓用高級分析才能看到如下內容:框架

  • 事件時間軸
  • 分配的對象數
  • 垃圾回收事件

在Android 8.0及更高版本上,高級分析在可調試的應用程序上始終開啓。jvm

如何計算內存

你在Memory Profiler頂部看到的數字基於您的應用經過Android 系統提交的全部私有內存頁面。此計數不包括與系統或其餘應用程序共享的頁面。

memory-profiler-counts_2x.png

內存計數中的類別以下:

  • Java:從Java或Kotlin代碼中分配對象的內存。
  • Native:從C或C++代碼分配對象的內存。即便您在應用程序中不使用C++,也可能會看到一些本地內存,由於Android Framework表明你使用Native內存來處理各類任務,例如處理圖像資源和其餘圖形,即便您編寫的代碼是Java或KOTLIN。
  • Graphics:用於在屏幕上顯示(包括GL曲面、GL紋理等)的圖形緩衝隊列的內存。(請注意,這是與CPU共享的內存,而不是專用的GPU內存。)
  • Stack:應用程序中Native堆棧和Java堆棧使用的內存。這一般與應用程序正在運行的線程數有關。
  • Code:應用程序用於代碼和資源的內存,例如dex字節碼、優化或編譯的dex代碼、.so庫和字體。
  • Others:系統不肯定如何分類的應用程序使用的內存。
  • Allocated:應用程序分配的Java/Kotlin對象數,不計算在C或C++中分配的對象。當鏈接到運行Android7.1及更低版本的設備時,此分配計數僅在Memory Profiler鏈接到正在運行的應用程序時開始。所以,在開始分析以前分配的任何對象都不會被考慮在內。可是,Android 8.0及更高版本包含一個設備內置分析工具,該工具可跟蹤全部分配。所以在android8.0及更高版本上,此數字始終表示應用程序中Java對象總數。

與以前Android Monitor中內存工具的計數相比,新的Memory Profiler以不一樣的方式記錄您的內存。於是,內存使用率看起來會更高了。Memory Profiler監視一些額外的類別,這些類別增長了總的內存。可是若是您只關心Java堆內存,那麼「Java」數值應該與前一個工具中的值相似。可是Java數值可能與您在Android Monitor中看到的不徹底匹配,新數值統計了自從Zygote派生以來應用程序的Java堆分配的全部物理頁面。所以,它提供了應用程序實際使用的物理內存量的精確表示。

注意:使用搭載 Android 8.0(API 級別 26)及更高版本的設備時,Memory Profiler 還會顯示應用中的一些誤報的原生內存使用量,而這些內存其實是分析工具使用的。對於大約 100000 個對象,最多會使報告的內存使用量增長 10MB。在 IDE 的將來版本中,這些數字將從您的數據中過濾掉。

查看內存分配

內存分配向您展現了內存中的每一個Java對象和JNI引用是如何分配。具體來講,Memory Profiler能夠向您顯示如下有關對象分配的信息:

  • 分配了哪些類型的對象以及它們使用了多少空間。
  • 每一個分配的堆棧跟蹤,包括在哪一個線程中。
  • 對象被釋放時間(僅當使用Android 8.0或更高版本的設備時)。

若是您的設備運行的是Android 8.0或更高版本,您能夠隨時查看對象分配,以下所示:在時間軸中拖動以選擇要查看分配的區域。不須要開始錄製會話,由於Android8.0及更高版本包含一個設備內置分析工具,能夠不斷跟蹤應用程序的分配。詳細內容能夠參考視頻:高版本查看內存分配

若是您的設備運行的是Android 7.1或更低版本,請單擊Memory Profiler工具欄中的Record memory allocations圖標。錄製時,Memory Profiler會跟蹤應用程序中發生的全部分配。完成後,單擊Stop recording圖標以查看分配。詳細內容能夠參考視頻:低版本查看內存分配

選擇時間線的某個區域後(或在使用運行Android7.1或更低版本的設備完成錄製會話時),已分配對象的列表將顯示在時間線下方,按類名分組並按堆計數排序。

注意:在 Android 7.1 及更低版本上,您最多能夠記錄 65535 個分配。若是您的記錄會話超出此限制,則記錄中僅保存最新的 65535 個分配。(在 Android 8.0 及更高版本上,則沒有實際的限制。)

要檢查分配記錄,請執行如下步驟:

  1. 瀏覽列表以查找堆計數異常大且可能泄漏的對象。若要查找已知類,請單擊Class Name列標題按字母順序排序,而後單擊類名。Instance View窗格將出如今右側,顯示該類的每一個實例,以下圖所示。或者,能夠經過單擊Filter 圖標或按 Ctrl+F 鍵,並在搜索字段中輸入類或包名稱來快速定位對象。若是從下拉菜單中選擇Arrange by callstack,還能夠按方法名進行搜索。若是要使用正則表達式,請選中Regex旁邊的複選框。若是搜索查詢區分大小寫,請選中Match case旁邊的複選框。
  2. Instance View窗格中,單擊實例。Call Stack標籤將出如今下面,顯示該實例的分配位置和線程。
  3. Call Stack標籤中,右鍵單擊任意行,而後選擇Jump to Source,就能夠在編輯器中打開該代碼。

memory-profiler-allocations-detail_2x.png

您可使用已分配對象列表上方的兩個菜單來選擇要檢查的堆以及如何組織數據。

從左側的菜單中,選擇要檢查的堆:

  • default heap:系統未指定堆時。
  • image heap:系統啓動鏡像,包含在啓動期間預加載的類。這裏的分配保證不會移動或消失。
  • zygote heap:當應用程序進程從Android系統中派生出來時的寫時拷貝堆。
  • app heap:應用程序分配內存的主堆。
  • JNI heap:這個堆顯示了Java Native接口(JNI)引用的分配和釋放的位置。

從右側的菜單中,選擇如何組織分配:

  • Arrange by class:根據類名對全部分配進行分組。這是默認設置。
  • Arrange by package:根據包名稱對全部分配進行分組。
  • Arrange by callstack:將全部分配分組到其相應的調用堆棧中。

在分析時提升應用程序性能

爲了提升分析時的應用程序性能,默認狀況下,內存探查器按期對內存分配進行採樣。在運行API級別26或更高級別的設備上測試時,可使用Allocation Tracking下拉列表更改此行爲。可用選項以下:

  • Full:捕獲內存中的全部對象分配。這是Android Studio 3.2和更早版本中的默認行爲。若是您的應用程序分配了不少對象,那麼在分析時,您可能會看到應用程序的速度明顯減慢。
  • Sampled:按期採樣內存中的對象分配。這是默認選項,在分析時對應用程序性能的影響較小。在短期內分配大量對象的應用程序可能仍然會顯示出明顯的減速。
  • Off:中止跟蹤應用程序的內存分配。
注意:默認狀況下,Android Studio在執行CPU錄製時中止跟蹤實時分配,並在CPU錄製完成後將其從新打開。您能夠在「CPU記錄配置」對話框中更改此行爲。

查看全局JNI引用

Java Native Interface(JNI)是一個容許Java代碼和Native代碼相互調用的框架。JNI引用是由Native代碼進行管理的,所以Native代碼使用的Java對象可能會存活很長時間。若是在沒有顯式刪除JNI引用的狀況下丟棄JNI引用,Java堆上的某些對象可能會變得不可訪問。此外,還可能會耗盡全局JNI引用的限制。要解決此類問題,請使用Memory Profiler中的JNI heap來瀏覽全部全局JNI引用,並按Java類型和Native調用堆棧篩選它們。有了這些信息,您能夠找到什麼時候何地建立和刪除全局JNI引用。

當應用程序運行時,選擇要檢查的時間軸的一部分,而後從類列表上方的下拉菜單中選擇JNI heap。接下來,您就能夠像往常同樣檢查堆中的對象,並雙擊Allocation Call Stack選項卡中的對象,查看在代碼中JNI引用的分配和釋放的位置,以下圖所示。
memory-profiler-jni-heap_2x.png

要檢查應用程序JNI代碼的內存分配,必須將應用程序部署到運行Android 8.0或更高版本的設備上。

抓取堆信息

堆信息能顯示在抓取堆信息時應用程序中的哪些對象正在使用內存。特別是在長時間的用戶會話以後,經過分析堆信息中是否存在您認爲不該該存在的對象,能夠用來幫助識別內存泄漏。抓取堆信息後,你能夠查看如下內容:

  • 你的應用程序分配了哪些類型的對象,以及每種對象的數量。
  • 每一個對象正在使用的內存量。
  • 在代碼中保存對每一個對象的引用的位置。
  • 分配對象的調用堆棧。(調用堆棧當前僅在Android 7.1及更低版本中提供,只有在分配期間抓取堆信息才能顯示。)

要抓取堆信息,請單擊Memory Profiler工具欄中的Dump Java heap圖標。在抓取期間,Java內存量可能會臨時增長。這是正常的,由於堆抓取發生在與應用程序相同的進程中,須要一些內存來收集數據。堆信息在內存時間軸的下方,顯示堆中全部類的類型,以下圖所示。

memory-profiler-dump_2x.png

若是須要更精確地瞭解堆的抓取時間,能夠經過調用dumpHprofData()在應用程序代碼的關鍵點抓取堆信息。

在類列表中,能夠看到如下信息:

  • Allocations:堆中的分配數。
  • Native Size:此對象類型使用的Native內存總量(字節)。此列僅適用於Android 7.0及更高版本。這裏您將看到Java中分配的一些對象的內存,由於Android在一些framework類(如位圖)使用Native內存。
  • 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列排序。若要按其餘列中的值排序,請單擊該列的標題。

單擊類名打開右側的IInstance View窗口(以下圖所示),每一個列出的實例包括如下內容:

  • Depth:從任意GC根到所選實例的最短躍點數。
  • Native Size:Native內存中此實例的大小。此列僅適用於Android 7.0及更高版本。
  • Shallow Size:這個實例在Java內存中的大小。
  • Retained Size:此實例支配的內存大小(根據支配樹)。
注意:默認狀況下,堆信息不會向您顯示每一個已分配對象的堆棧軌跡。要獲取堆棧軌跡,在點擊 Dump Java heap 以前,您必須先開始記錄內存分配。而後,您能夠在 Instance View 中選擇一個實例,並查看 References 標籤旁邊的 Call Stack 標籤,以下圖所示。不過,在您開始記錄分配以前,可能已分配一些對象,所以不會顯示這些對象的調用堆棧。在包含調用堆棧的實例在圖標上會有一個「堆棧」標誌表示。(遺憾的是,因爲堆棧軌跡須要您執行分配記錄,所以您目前沒法在 Android 8.0 上查看堆信息的堆棧軌跡。)

memory-profiler-dump-stacktrace_2x.png

要檢查堆信息,請執行如下步驟:

  1. 瀏覽列表以查找堆計數異常大且可能泄漏的對象。若要查找已知類,請單擊Class Name列標題按字母順序排序。而後單擊類名。Instance View窗格會出如今右側,顯示該類的每一個實例,如上圖所示。或者,能夠經過單擊Filter圖標或按Control+F,並在搜索字段中輸入類或包名稱來快速定位對象。若是從下拉菜單中選擇Arrange by callstack,還能夠按方法名進行搜索。若是要使用正則表達式,請選中Regex旁邊的複選框。若是搜索查詢區分大小寫,請選中Match case旁邊的複選框。
  2. Instance View窗格中,單擊實例。References標籤將出如今下面,顯示對該對象的每一個引用。或者,單擊實例名稱旁邊的箭頭以查看其全部字段,而後單擊字段名稱以查看其全部引用。若是要查看某個字段的實例詳細信息,請右鍵單擊該字段並選擇Go to Instance
  3. References標籤中,若是標識了可能泄漏內存的引用,請右鍵單擊該引用並選擇Go to Instance。這將從堆信息中選擇相應的實例,從而顯示其本身的實例數據。

在堆信息中,注意如下狀況可能致使的內存泄漏:

  • 對Activity、Context、View、Drawable和其餘對象的長時間引用可能包含對Activity或Context的引用。
  • 能夠保存Activity的非靜態內部類,如Runnable。
  • 保存對象的時間超過所需時長的緩存。

將堆信息另存爲HPROF文件

捕獲堆信息後,只有在Profiler運行時,數據才能在Memory Profiler中查看。退出剖析會話時,將丟失堆數據。所以,若是您想保存它以便之後查看,請將堆信息導出到HPROF文件。在Android Studio 3.1及更低版本中,Export capture to file按鈕位於時間軸下的工具欄左側;在Android studio 3.2及更高版本中,Sessions窗格中每一個Heap Dump的右側都有一個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)文件,請單擊Sessions窗格中的Start a new profiling session圖標,選擇Load from file,而後從文件瀏覽器中選擇該文件。也能夠經過將HPROF文件從文件瀏覽器拖動到編輯器窗口中來導入該文件。

分析內存的技巧

在使用Memory Profiler時,您應該給應用程序代碼增長壓力,試圖去暴露內存泄漏。引起應用程序內存泄漏的一種方法是在檢查堆以前讓它運行一段時間,泄漏可能會逐漸聚集到堆中分配的頂部。可是當泄漏越小時,應用程序就須要運行越長時間,再進行泄漏檢查。

您還能夠經過如下方式之一觸發內存泄漏:

  • 在不一樣的Activity狀態下,將設備從縱向旋轉到橫向,再旋轉到縱向,如此進行屢次旋轉。旋轉設備一般會致使應用程序泄漏Activity、Context 或 View對象,由於系統會從新建立該Activity,若是應用程序在其餘地方保存了一個對象的引用,則系統沒法對其進行垃圾回收。
  • 在處於不一樣Activity狀態時在應用程序之間進行切換(導航到主屏幕,而後返回您的應用程序)。

參考文檔:

Android Developers: memory profiler

相關文章
相關標籤/搜索