目錄
前言
Android 存在內(nèi)存回收機制猴伶,當它確定應用不再使用某些對象時,垃圾回收器會將未使用的內(nèi)存釋放回堆中。 雖然 Android 查找未使用內(nèi)存的方式在不斷改進稳其,但對于所有 Android 版本扩氢,系統(tǒng)都必須在某個時間點短暫地暫停你寫的代碼耕驰。 大多數(shù)情況下,這些暫停難以察覺录豺。 但是朦肘,如果你的應用分配內(nèi)存的速度比系統(tǒng)回收內(nèi)存的速度快饭弓,那么當釋放足夠的內(nèi)存以滿足應用的分配需要時,應用就可能出現(xiàn)延遲媒抠。 這樣可能會導致應用跳幀弟断,并使系統(tǒng)明顯變慢
如果存在內(nèi)存泄漏,則即使應用在后臺運行也會保留該內(nèi)存领舰。 此行為會強制執(zhí)行不必要的垃圾回收事件夫嗓,因而拖慢系統(tǒng)的內(nèi)存性能。 最后冲秽,系統(tǒng)被迫終止你的應用進程以回收內(nèi)存舍咖。 然后,當用戶返回你的應用時锉桑,就必須完全重啟
為幫助防止這些問題排霉,我們可以使用Memory Profiler
- 實時圖表展示應用內(nèi)存使用量
- 識別內(nèi)存泄漏、抖動
- 提供捕獲堆轉儲民轴、強制GC以及跟蹤內(nèi)存分配的能力
Memory Profiler 概覽
如圖 1 所示攻柠,Memory Profiler 的默認視圖包括以下各項:
強制執(zhí)行垃圾回收
堆轉儲,把內(nèi)存信息通過文件的方式保存下來后裸,可以進行分析
記錄內(nèi)存分配情況瑰钮, 此按鈕僅在連接至運行 Android 7.1 或更低版本的設備時才會顯示
放大/縮小時間線
跳轉至實時內(nèi)存數(shù)據(jù)
Event 時間線,顯示 Activity 狀態(tài)微驶、用戶輸入 Event 和屏幕旋轉 Event
-
內(nèi)存使用量時間線浪谴,其包含以下內(nèi)容:
一個顯示每個內(nèi)存類別使用多少內(nèi)存的堆疊圖表,如左側的 y 軸以及頂部的彩色鍵所示
虛線表示分配的對象數(shù)因苹,如右側的 y 軸所示
用于表示每個垃圾回收 Event 的圖標
如何計算內(nèi)存占用
內(nèi)存計數(shù)中的類別如下所示:
Java:從 Java 或 Kotlin 代碼分配的對象內(nèi)存
-
Native:從 C 或 C++ 代碼分配的對象內(nèi)存
即使你的應用中不使用 C++苟耻,你也可能會看到此處使用的一些原生內(nèi)存,因為 Android 框架使用原生內(nèi)存代表您處理各種任務扶檐,如處理圖像資源和其他圖形時凶杖,即使你編寫的代碼采用 Java 或 Kotlin 語言
Graphics:圖形緩沖區(qū)隊列向屏幕顯示像素(包括 GL 表面、GL 紋理等等)所使用的內(nèi)存 (請注意款筑,這是與 CPU 共享的內(nèi)存智蝠,不是 GPU 專用內(nèi)存)
Stack: 應用中的原生堆棧和 Java 堆棧使用的內(nèi)存。 這通常與您的應用運行多少線程有關
Code:應用用于處理代碼和資源(如 dex 字節(jié)碼奈梳、已優(yōu)化或已編譯的 dex 碼寻咒、.so 庫和字體)的內(nèi)存
Other:應用使用的系統(tǒng)不確定如何分類的內(nèi)存
-
Allocated:您的應用分配的 Java/Kotlin 對象數(shù)。 它沒有計入 C 或 C++ 中分配的對象
當連接至運行 Android 7.1 及更低版本的設備時颈嚼,此分配僅在 Memory Profiler 連接至你運行的應用時才開始計數(shù)。 因此饭寺,你開始分析之前分配的任何對象都不會被計入阻课。 不過叫挟,Android 8.0 附帶一個設備內(nèi)置分析工具,該工具可記錄所有分配限煞,因此抹恳,在 Android 8.0 及更高版本上,此數(shù)字始終表示你的應用中待處理的 Java 對象總數(shù)
Java 數(shù)字可能與你在 Android Monitor 中看到的數(shù)字并非完全相同署驻,這是因為應用的 Java 堆是從 Zygote 啟動的奋献,而新數(shù)字則計入了為它分配的所有物理內(nèi)存頁面。 因此旺上,它可以準確反映你的應用實際使用了多少物理內(nèi)存
查看內(nèi)存分配
要檢查內(nèi)存分配記錄瓶蚂,可以按以下步驟操作:
- 瀏覽列表以查找堆計數(shù)異常大且可能存在泄漏的對象。 點擊 Class Name 列標題以按字母順序排序宣吱。 然后點擊一個類名稱窃这。 此時在右側將出現(xiàn) Instance View 窗格,顯示該類的每個實例征候,如圖 3 中所示
- 在 Instance View 窗格中杭攻,點擊一個實例。 此時下方將出現(xiàn) Call Stack 標簽疤坝,顯示該實例被分配到何處以及哪個線程中
- 在 Call Stack 標簽中兆解,點擊任意行以在編輯器中跳轉到該代碼
默認情況下,左側的分配列表按類名稱排列跑揉。 在列表頂部锅睛,你可以使用右側的下拉列表在以下排列方式之間進行切換:
- Arrange by class:基于類名稱對所有分配進行分組
- Arrange by package:基于軟件包名稱對所有分配進行分組
- Arrange by callstack:將所有分配分組到其對應的調用堆棧
捕獲堆轉儲
堆轉儲顯示在您捕獲堆轉儲時您的應用中哪些對象正在使用內(nèi)存
要捕獲堆轉儲,在 Memory Profiler 工具欄中點擊 Dump Java heap
注:如果您需要更精確地了解轉儲的創(chuàng)建時間具练,可以通過調用
Debug.dumpHprofData()
在應用代碼的關鍵點創(chuàng)建堆轉儲
要檢查堆信息,請按以下步驟操作:
- 瀏覽列表以查找堆計數(shù)異常大且可能存在泄漏的對象甜无。 為幫助查找已知類扛点,點擊 Class Name 列標題以按字母順序排序。 然后點擊一個類名稱岂丘。 此時在右側將出現(xiàn) Instance View 窗格陵究,顯示該類的每個實例,如圖 5 中所示
- 在Instance View窗格中奥帘,點擊一個實例铜邮。此時下方將出現(xiàn)References,顯示該對象的每個引用
- 在 References 標簽中,如果您發(fā)現(xiàn)某個引用可能在泄漏內(nèi)存松蒜,則右鍵點擊它并選擇 Go to Instance
在堆轉儲中扔茅,請注意由下列任意情況引起的內(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é)為單位)
在類列表頂部雅倒,你可以使用左側下拉列表在以下堆轉儲之間進行切換:
- Default heap:系統(tǒng)未指定堆時
- App heap:應用在其中分配內(nèi)存的主堆
- Image heap:系統(tǒng)啟動映像,包含啟動期間預加載的類攀芯。 此處的分配保證絕不會移動或消失
- Zygote heap:寫時復制堆屯断,其中的應用進程是從 Android 系統(tǒng)中派生的
在 Instance View 中,每個實例都包含以下信息:
- Depth:從任意 GC 根到所選實例的最短 hop 數(shù)
- Shallow Size:此實例的大小
- Retained Size:此實例支配的內(nèi)存大小
分析內(nèi)存的技巧
使用 Memory Profiler 時侣诺,你可以應用代碼施加壓力并嘗試強制內(nèi)存泄漏殖演。 在應用中引發(fā)內(nèi)存泄漏的一種方式是,先讓其運行一段時間年鸳,然后再檢查堆趴久。 泄漏在堆中可能逐漸匯聚到分配頂部。 不過搔确,泄漏越小彼棍,你越需要運行更長時間的應用才能看到泄漏
您還可以通過以下方式之一觸發(fā)內(nèi)存泄漏:
- 將設備從縱向旋轉為橫向,然后在不同的 Activity 狀態(tài)下反復操作多次膳算。 旋轉設備經(jīng)常會導致應用泄漏
Activity
座硕、Context
或View
對象,因為系統(tǒng)會重新創(chuàng)建Activity
涕蜂,而如果您的應用在其他地方保持對這些對象之一的引用华匾,系統(tǒng)將無法對其進行垃圾回收 - 處于不同的 Activity 狀態(tài)時,在您的應用與另一個應用之間切換(導航到主屏幕机隙,然后返回到您的應用)