一甥雕、簡介
Memory Profiler 是 Android Profiler 中的一個組件盏道,可幫助您識別導(dǎo)致應(yīng)用卡頓、凍結(jié)甚至崩潰的內(nèi)存泄漏和流失互艾。 它顯示一個應(yīng)用內(nèi)存使用量的實時圖表试和,讓您可以捕獲堆轉(zhuǎn)儲、強(qiáng)制執(zhí)行垃圾回收以及跟蹤內(nèi)存分配忘朝。
二灰署、為什么應(yīng)分析您的應(yīng)用內(nèi)存
Android 提供一個托管內(nèi)存環(huán)境—當(dāng)它確定您的應(yīng)用不再使用某些對象時,垃圾回收器會將未使用的內(nèi)存釋放回堆中。 雖然 Android 查找未使用內(nèi)存的方式在不斷改進(jìn)溉箕,但對于所有 Android 版本晦墙,系統(tǒng)都必須在某個時間點短暫地暫停您的代碼。 大多數(shù)情況下肴茄,這些暫停難以察覺晌畅。 不過,如果您的應(yīng)用分配內(nèi)存的速度比系統(tǒng)回收內(nèi)存的速度快寡痰,則當(dāng)收集器釋放足夠的內(nèi)存以滿足您的分配需要時抗楔,您的應(yīng)用可能會延遲。 此延遲可能會導(dǎo)致您的應(yīng)用跳幀拦坠,并使系統(tǒng)明顯變慢连躏。
盡管您的應(yīng)用不會表現(xiàn)出變慢,但如果存在內(nèi)存泄漏贞滨,則即使應(yīng)用在后臺運行也會保留該內(nèi)存入热。 此行為會強(qiáng)制執(zhí)行不必要的垃圾回收 Event,因而拖慢系統(tǒng)的內(nèi)存性能晓铆。 最后勺良,系統(tǒng)被迫終止您的應(yīng)用進(jìn)程以回收內(nèi)存。 然后骄噪,當(dāng)用戶返回您的應(yīng)用時尚困,它必須完全重啟。
為幫助防止這些問題链蕊,您應(yīng)使用 Memory Profiler 執(zhí)行以下操作:
- 在時間線中查找可能會導(dǎo)致性能問題的不理想的內(nèi)存分配模式事甜。
- 轉(zhuǎn)儲 Java 堆以查看在任何給定時間哪些對象耗盡了使用內(nèi)存。 長時間進(jìn)行多個堆轉(zhuǎn)儲可幫助識別內(nèi)存泄漏滔韵。
- 記錄正常用戶交互和極端用戶交互期間的內(nèi)存分配以準(zhǔn)確識別您的代碼在何處短時間分配了過多對象讳侨,或分配了泄漏的對象。
三奏属、Memory Profiler 概覽
要打開 Memory Profiler跨跨,可以以下步驟操作:
當(dāng)您首次打開 Memory Profiler 時,您將看到一條表示應(yīng)用內(nèi)存使用量的詳細(xì)時間線囱皿,并可訪問用于強(qiáng)制執(zhí)行垃圾回收勇婴、捕捉堆轉(zhuǎn)儲和記錄內(nèi)存分配的各種工具。
如圖 1 所示嘱腥,Memory Profiler 的默認(rèn)視圖包括以下各項:
- 用于強(qiáng)制執(zhí)行垃圾回收 Event 的按鈕耕渴。
- 用于捕獲堆轉(zhuǎn)儲的按鈕。
- 用于記錄內(nèi)存分配情況的按鈕齿兔。 此按鈕僅在連接至運行 Android 7.1 或更低版本的設(shè)備時才會顯示橱脸。
- 用于放大/縮小時間線的按鈕础米。
- 用于跳轉(zhuǎn)至實時內(nèi)存數(shù)據(jù)的按鈕。
- Event 時間線添诉,其顯示 Activity 狀態(tài)屁桑、用戶輸入 Event 和屏幕旋轉(zhuǎn) Event。
- 內(nèi)存使用量時間線栏赴,其包含以下內(nèi)容:
- 一個顯示每個內(nèi)存類別使用多少內(nèi)存的堆疊圖表蘑斧,如左側(cè)的 y 軸以及頂部的彩色鍵所示。
- 虛線表示分配的對象數(shù)须眷,如右側(cè)的 y 軸所示竖瘾。
- 用于表示每個垃圾回收 Event 的圖標(biāo)。
如何計算內(nèi)存
您在 Memory Profiler 頂部看到的數(shù)字取決于您的應(yīng)用根據(jù) Android 系統(tǒng)機(jī)制所創(chuàng)建的所有私有內(nèi)存花颗。 此計數(shù)不包含與系統(tǒng)或其他應(yīng)用共享的內(nèi)存捕传。
內(nèi)存計數(shù)中的類別如下所示:
- Java:從 Java 或 Kotlin 代碼分配的對象內(nèi)存。
- Native:從 C 或 C++ 代碼分配的對象內(nèi)存扩劝。
即使您的應(yīng)用中不使用 C++乐横,您也可能會看到此處使用的一些原生內(nèi)存,如處理圖像資源和其他圖形時今野,即使您編寫的代碼采用 Java 或 Kotlin 語言,Android 框架也會使用原生內(nèi)存處理各種任務(wù)罐农。 - Graphics:圖形緩沖區(qū)隊列向屏幕顯示像素(包括 GL 表面条霜、GL 紋理等等)所使用的內(nèi)存。 (請注意涵亏,這是與 CPU 共享的內(nèi)存宰睡,不是 GPU 專用內(nèi)存。)
- Stack: 您的應(yīng)用中的原生堆棧和 Java 堆棧使用的內(nèi)存气筋。 這通常與您的應(yīng)用運行多少線程有關(guān)拆内。
- Code:您的應(yīng)用用于處理代碼和資源(如 dex 字節(jié)碼、已優(yōu)化或已編譯的 dex 碼宠默、.so 庫和字體)的內(nèi)存麸恍。
- Other:您的應(yīng)用使用的系統(tǒng)不確定如何分類的內(nèi)存。
- Allocated:您的應(yīng)用分配的 Java/Kotlin 對象數(shù)搀矫。 它沒有計入 C 或 C++ 中分配的對象抹沪。
四、查看內(nèi)存分配
內(nèi)存分配顯示內(nèi)存中每個對象是如何分配的瓤球。 具體而言融欧,Memory Profiler 可為您顯示有關(guān)對象分配的以下信息:
- 分配哪些類型的對象以及它們使用多少空間。
- 每個分配的堆疊追蹤卦羡,包括在哪個線程中噪馏。
- 對象在何時被取消分配(僅當(dāng)使用運行 Android 8.0 或更高版本的設(shè)備時)麦到。
如果您的設(shè)備運行 Android 8.0 或更高版本,您可以隨時按照下述方法查看您的對象分配: 只需點擊并按住時間線欠肾,并拖動選擇您想要查看分配的區(qū)域(如下圖所示)瓶颠。 不需要開始記錄會話,因為 Android 8.0 及更高版本附帶設(shè)備內(nèi)置分析工具董济,可持續(xù)跟蹤您的應(yīng)用分配步清。
要檢查分配記錄,請按以下步驟操作:
- 瀏覽列表以查找堆計數(shù)異常大且可能存在泄漏的對象虏肾。 為幫助查找已知類廓啊,點擊 Class Name 列標(biāo)題以按字母順序排序。 然后點擊一個類名稱封豪。 此時在右側(cè)將出現(xiàn) Instance View 窗格谴轮,顯示該類的每個實例,如圖中所示吹埠。
- 在 Instance View 窗格中第步,點擊一個實例。 此時下方將出現(xiàn) Call Stack 標(biāo)簽缘琅,顯示該實例被分配到何處以及哪個線程中粘都。
-
在 Call Stack 標(biāo)簽中,點擊任意行以在編輯器中跳轉(zhuǎn)到該代碼刷袍。
image.png
默認(rèn)情況下翩隧,左側(cè)的分配列表按類名稱排列。 在列表頂部呻纹,您可以使用右側(cè)的下拉列表在以下排列方式之間進(jìn)行切換:
- Arrange by class:基于類名稱對所有分配進(jìn)行分組堆生。
- Arrange by package:基于軟件包名稱對所有分配進(jìn)行分組。
- Arrange by callstack:將所有分配分組到其對應(yīng)的調(diào)用堆棧雷酪。
五淑仆、捕獲堆轉(zhuǎn)儲
堆轉(zhuǎn)儲顯示在您捕獲堆轉(zhuǎn)儲時您的應(yīng)用中哪些對象正在使用內(nèi)存。 特別是在長時間的用戶會話后哥力,堆轉(zhuǎn)儲會顯示您認(rèn)為不應(yīng)再位于內(nèi)存中卻仍在內(nèi)存中的對象蔗怠,從而幫助識別內(nèi)存泄漏。 在捕獲堆轉(zhuǎn)儲后吩跋,您可以查看以下信息:
- 您的應(yīng)用已分配哪些類型的對象蟀淮,以及每個類型分配多少。
- 每個對象正在使用多少內(nèi)存钞澳。
- 在代碼中的何處仍在引用每個對象怠惶。
-
對象所分配到的調(diào)用堆棧。 (目前轧粟,如果您在記錄分配時捕獲堆轉(zhuǎn)儲策治,則只有在 Android 7.1 及更低版本中脓魏,堆轉(zhuǎn)儲才能使用調(diào)用堆棧。)
查看堆轉(zhuǎn)儲.png
要捕獲堆轉(zhuǎn)儲通惫,在 Memory Profiler 工具欄中點擊 Dump Java heap 茂翔。 在轉(zhuǎn)儲堆期間,Java 內(nèi)存量可能會暫時增加履腋。 這很正常珊燎,因為堆轉(zhuǎn)儲與您的應(yīng)用發(fā)生在同一進(jìn)程中,并需要一些內(nèi)存來收集數(shù)據(jù)遵湖。
堆轉(zhuǎn)儲顯示在內(nèi)存時間線下悔政,顯示堆中的所有類類型,如上圖所示延旧。
如果您需要更精確地了解轉(zhuǎn)儲的創(chuàng)建時間谋国,可以通過調(diào)用 dumpHprofData() 在應(yīng)用代碼的關(guān)鍵點創(chuàng)建堆轉(zhuǎn)儲。
要檢查您的堆迁沫,請按以下步驟操作:
- 瀏覽列表以查找堆計數(shù)異常大且可能存在泄漏的對象芦瘾。 為幫助查找已知類,點擊 Class Name 列標(biāo)題以按字母順序排序集畅。 然后點擊一個類名稱近弟。 此時在右側(cè)將出現(xiàn) Instance View 窗格,顯示該類的每個實例挺智,如圖 5 中所示祷愉。
- 在 Instance View 窗格中,點擊一個實例逃贝。此時下方將出現(xiàn) References,顯示該對象的每個引用迫摔。
或者沐扳,點擊實例名稱旁的箭頭以查看其所有字段,然后點擊一個字段名稱查看其所有引用句占。 如果您要查看某個字段的實例詳情沪摄,右鍵點擊該字段并選擇 Go to Instance。 - 在 References 標(biāo)簽中纱烘,如果您發(fā)現(xiàn)某個引用可能在泄漏內(nèi)存杨拐,則右鍵點擊它并選擇 Go to Instance。 這將從堆轉(zhuǎn)儲中選擇對應(yīng)的實例擂啥,顯示您自己的實例數(shù)據(jù)哄陶。
默認(rèn)情況下,堆轉(zhuǎn)儲不會向您顯示每個已分配對象的堆疊追蹤哺壶。 要獲取堆疊追蹤屋吨,在點擊 Dump Java heap 之前蜒谤,您必須先開始記錄內(nèi)存分配。 然后至扰,您可以在 Instance View 中選擇一個實例鳍徽,并查看 Call Stack 標(biāo)簽以及 References 標(biāo)簽,如圖 5 所示敢课。不過阶祭,在您開始記錄分配之前,可能已分配一些對象直秆,因此濒募,調(diào)用堆棧不能用于這些對象。 包含調(diào)用堆棧的實例在圖標(biāo)
上用一個“堆椙欣澹”標(biāo)志表示萨咳。 (遺憾的是,由于堆疊追蹤需要您執(zhí)行分配記錄疫稿,因此培他,您目前無法在 Android 8.0 上查看堆轉(zhuǎn)儲的堆疊追蹤。)
在您的堆轉(zhuǎn)儲中遗座,請注意由下列任意情況引起的內(nèi)存泄漏:
- 長時間引用
Activity
舀凛、Context
、View
途蒋、Drawable
和其他對象猛遍,可能會保持對Activity
或Context
容器的引用。 - 可以保持
Activity
實例的非靜態(tài)內(nèi)部類号坡,如Runnable
懊烤。 - 對象保持時間超出所需時間的緩存。
在類列表中宽堆,您可以查看以下信息:
- Heap Count:堆中的實例數(shù)腌紧。
- Shallow Size:此堆中所有實例的總大小(以字節(jié)為單位)畜隶。
- Retained Size:為此類的所有實例而保留的內(nèi)存總大斜诶摺(以字節(jié)為單位)。
在類列表頂部籽慢,您可以使用左側(cè)下拉列表在以下堆轉(zhuǎn)儲之間進(jìn)行切換:
- Default heap:系統(tǒng)未指定堆時浸遗。
- App heap:您的應(yīng)用在其中分配內(nèi)存的主堆。
- Image heap:系統(tǒng)啟動映像箱亿,包含啟動期間預(yù)加載的類跛锌。 此處的分配保證絕不會移動或消失。
- Zygote heap:寫時復(fù)制堆届惋,其中的應(yīng)用進(jìn)程是從 Android 系統(tǒng)中派生的察净。
默認(rèn)情況下驾茴,此堆中的對象列表按類名稱排列。 您可以使用其他下拉列表在以下排列方式之間進(jìn)行切換:
- Arrange by class:基于類名稱對所有分配進(jìn)行分組氢卡。
- Arrange by package:基于軟件包名稱對所有分配進(jìn)行分組锈至。
- Arrange by callstack:將所有分配分組到其對應(yīng)的調(diào)用堆棧。 此選項僅在記錄分配期間捕獲堆轉(zhuǎn)儲時才有效译秦。 即使如此峡捡,堆中的對象也很可能是在您開始記錄之前分配的,因此這些分配會首先顯示筑悴,且只按類名稱列出们拙。
默認(rèn)情況下,此列表按 Retained Size 列排序阁吝。 您可以點擊任意列標(biāo)題以更改列表的排序方式砚婆。
在 Instance View 中,每個實例都包含以下信息:
- Depth:從任意 GC 根到所選實例的最短 hop 數(shù)突勇。
- Shallow Size:此實例的大小装盯。
- Retained Size:此實例支配的內(nèi)存大小
將堆轉(zhuǎn)儲另存為 HPROF
要使用其他 HPROF 分析器(如MAT),您需要將 HPROF 文件從 Android 格式轉(zhuǎn)換為 Java SE HPROF 格式甲馋。 您可以使用 android_sdk/platform-tools/ 目錄中提供的 hprof-conv 工具執(zhí)行此操作埂奈。 運行包括以下兩個參數(shù)的 hprof-conv 命令:原始 HPROF 文件和轉(zhuǎn)換后 HPROF 文件的寫入位置。 例如:
hprof-conv heap-original.hprof heap-converted.hprof