Android內(nèi)存分析工具 — Memory Profiler

目錄

前言

Android 存在內(nèi)存回收機(jī)制,當(dāng)它確定應(yīng)用不再使用某些對(duì)象時(shí),垃圾回收器會(huì)將未使用的內(nèi)存釋放回堆中。 雖然 Android 查找未使用內(nèi)存的方式在不斷改進(jìn),但對(duì)于所有 Android 版本,系統(tǒng)都必須在某個(gè)時(shí)間點(diǎn)短暫地暫停你寫的代碼。 大多數(shù)情況下,這些暫停難以察覺。 但是,如果你的應(yīng)用分配內(nèi)存的速度比系統(tǒng)回收內(nèi)存的速度快,那么當(dāng)釋放足夠的內(nèi)存以滿足應(yīng)用的分配需要時(shí),應(yīng)用就可能出現(xiàn)延遲。 這樣可能會(huì)導(dǎo)致應(yīng)用跳幀,并使系統(tǒng)明顯變慢

如果存在內(nèi)存泄漏,則即使應(yīng)用在后臺(tái)運(yùn)行也會(huì)保留該內(nèi)存。 此行為會(huì)強(qiáng)制執(zhí)行不必要的垃圾回收事件,因而拖慢系統(tǒng)的內(nèi)存性能。 最后,系統(tǒng)被迫終止你的應(yīng)用進(jìn)程以回收內(nèi)存。 然后,當(dāng)用戶返回你的應(yīng)用時(shí),就必須完全重啟

為幫助防止這些問題,我們可以使用Memory Profiler

  • 實(shí)時(shí)圖表展示應(yīng)用內(nèi)存使用量
  • 識(shí)別內(nèi)存泄漏、抖動(dòng)
  • 提供捕獲堆轉(zhuǎn)儲(chǔ)、強(qiáng)制GC以及跟蹤內(nèi)存分配的能力

Memory Profiler 概覽

圖 1. Memory Profiler

如圖 1 所示,Memory Profiler 的默認(rèn)視圖包括以下各項(xiàng):

  1. 強(qiáng)制執(zhí)行垃圾回收

  2. 堆轉(zhuǎn)儲(chǔ),把內(nèi)存信息通過文件的方式保存下來,可以進(jìn)行分析

  3. 記錄內(nèi)存分配情況, 此按鈕僅在連接至運(yùn)行 Android 7.1 或更低版本的設(shè)備時(shí)才會(huì)顯示

  4. 放大/縮小時(shí)間線

  5. 跳轉(zhuǎn)至實(shí)時(shí)內(nèi)存數(shù)據(jù)

  6. Event 時(shí)間線,顯示 Activity 狀態(tài)、用戶輸入 Event 和屏幕旋轉(zhuǎn) Event

  7. 內(nèi)存使用量時(shí)間線,其包含以下內(nèi)容:

    • 一個(gè)顯示每個(gè)內(nèi)存類別使用多少內(nèi)存的堆疊圖表,如左側(cè)的 y 軸以及頂部的彩色鍵所示

    • 虛線表示分配的對(duì)象數(shù),如右側(cè)的 y 軸所示

    • 用于表示每個(gè)垃圾回收 Event 的圖標(biāo)

如何計(jì)算內(nèi)存占用

圖 2. Memory Profiler 頂部的內(nèi)存計(jì)數(shù)圖例

內(nèi)存計(jì)數(shù)中的類別如下所示:

  • Java:從 Java 或 Kotlin 代碼分配的對(duì)象內(nèi)存

  • Native:從 C 或 C++ 代碼分配的對(duì)象內(nèi)存

    即使你的應(yīng)用中不使用 C++,你也可能會(huì)看到此處使用的一些原生內(nèi)存,因?yàn)?Android 框架使用原生內(nèi)存代表您處理各種任務(wù),如處理圖像資源和其他圖形時(shí),即使你編寫的代碼采用 Java 或 Kotlin 語言

  • Graphics:圖形緩沖區(qū)隊(duì)列向屏幕顯示像素(包括 GL 表面、GL 紋理等等)所使用的內(nèi)存 (請(qǐng)注意,這是與 CPU 共享的內(nèi)存,不是 GPU 專用內(nèi)存)

  • Stack: 應(yīng)用中的原生堆棧和 Java 堆棧使用的內(nèi)存。 這通常與您的應(yīng)用運(yùn)行多少線程有關(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 對(duì)象數(shù)。 它沒有計(jì)入 C 或 C++ 中分配的對(duì)象

    當(dāng)連接至運(yùn)行 Android 7.1 及更低版本的設(shè)備時(shí),此分配僅在 Memory Profiler 連接至你運(yùn)行的應(yīng)用時(shí)才開始計(jì)數(shù)。 因此,你開始分析之前分配的任何對(duì)象都不會(huì)被計(jì)入。 不過,Android 8.0 附帶一個(gè)設(shè)備內(nèi)置分析工具,該工具可記錄所有分配,因此,在 Android 8.0 及更高版本上,此數(shù)字始終表示你的應(yīng)用中待處理的 Java 對(duì)象總數(shù)

Java 數(shù)字可能與你在 Android Monitor 中看到的數(shù)字并非完全相同,這是因?yàn)閼?yīng)用的 Java 堆是從 Zygote 啟動(dòng)的,而新數(shù)字則計(jì)入了為它分配的所有物理內(nèi)存頁面。 因此,它可以準(zhǔn)確反映你的應(yīng)用實(shí)際使用了多少物理內(nèi)存

查看內(nèi)存分配

要檢查內(nèi)存分配記錄,可以按以下步驟操作:

  1. 瀏覽列表以查找堆計(jì)數(shù)異常大且可能存在泄漏的對(duì)象。 點(diǎn)擊 Class Name 列標(biāo)題以按字母順序排序。 然后點(diǎn)擊一個(gè)類名稱。 此時(shí)在右側(cè)將出現(xiàn) Instance View 窗格,顯示該類的每個(gè)實(shí)例,如圖 3 中所示
  2. Instance View 窗格中,點(diǎn)擊一個(gè)實(shí)例。 此時(shí)下方將出現(xiàn) Call Stack 標(biāo)簽,顯示該實(shí)例被分配到何處以及哪個(gè)線程中
  3. Call Stack 標(biāo)簽中,點(diǎn)擊任意行以在編輯器中跳轉(zhuǎn)到該代碼
圖 3. 有關(guān)每個(gè)已分配對(duì)象的詳情顯示在右側(cè)的 **Instance View** 中

默認(rèn)情況下,左側(cè)的分配列表按類名稱排列。 在列表頂部,你可以使用右側(cè)的下拉列表在以下排列方式之間進(jìn)行切換:

  • Arrange by class:基于類名稱對(duì)所有分配進(jìn)行分組
  • Arrange by package:基于軟件包名稱對(duì)所有分配進(jìn)行分組
  • Arrange by callstack:將所有分配分組到其對(duì)應(yīng)的調(diào)用堆棧

捕獲堆轉(zhuǎn)儲(chǔ)

堆轉(zhuǎn)儲(chǔ)顯示在您捕獲堆轉(zhuǎn)儲(chǔ)時(shí)您的應(yīng)用中哪些對(duì)象正在使用內(nèi)存

要捕獲堆轉(zhuǎn)儲(chǔ),在 Memory Profiler 工具欄中點(diǎn)擊 Dump Java heap

。 在轉(zhuǎn)儲(chǔ)堆期間,Java 內(nèi)存量可能會(huì)暫時(shí)增加。因?yàn)槎艳D(zhuǎn)儲(chǔ)與您的應(yīng)用發(fā)生在同一進(jìn)程中,并需要一些內(nèi)存來收集數(shù)據(jù)

注:如果您需要更精確地了解轉(zhuǎn)儲(chǔ)的創(chuàng)建時(shí)間,可以通過調(diào)用 Debug.dumpHprofData() 在應(yīng)用代碼的關(guān)鍵點(diǎn)創(chuàng)建堆轉(zhuǎn)儲(chǔ)

要檢查堆信息,請(qǐng)按以下步驟操作:

  1. 瀏覽列表以查找堆計(jì)數(shù)異常大且可能存在泄漏的對(duì)象。 為幫助查找已知類,點(diǎn)擊 Class Name 列標(biāo)題以按字母順序排序。 然后點(diǎn)擊一個(gè)類名稱。 此時(shí)在右側(cè)將出現(xiàn) Instance View 窗格,顯示該類的每個(gè)實(shí)例,如圖 5 中所示
  2. Instance View窗格中,點(diǎn)擊一個(gè)實(shí)例。此時(shí)下方將出現(xiàn)References,顯示該對(duì)象的每個(gè)引用
  3. References 標(biāo)簽中,如果您發(fā)現(xiàn)某個(gè)引用可能在泄漏內(nèi)存,則右鍵點(diǎn)擊它并選擇 Go to Instance

在堆轉(zhuǎn)儲(chǔ)中,請(qǐng)注意由下列任意情況引起的內(nèi)存泄漏:

  • 長時(shí)間引用 ActivityContextViewDrawable 和其他對(duì)象,可能會(huì)保持對(duì) ActivityContext 容器的引用
  • 可以保持 Activity 實(shí)例的非靜態(tài)內(nèi)部類,如 Runnable
  • 對(duì)象保持時(shí)間超出所需時(shí)間的緩存
圖 4. 捕獲堆轉(zhuǎn)儲(chǔ)需要的持續(xù)時(shí)間標(biāo)示在時(shí)間線中

在類列表中,你可以查看以下信息:

  • Heap Count:堆中的實(shí)例數(shù)
  • Shallow Size:此堆中所有實(shí)例的總大小(以字節(jié)為單位)
  • Retained Size:為此類的所有實(shí)例而保留的內(nèi)存總大小(以字節(jié)為單位)

在類列表頂部,你可以使用左側(cè)下拉列表在以下堆轉(zhuǎn)儲(chǔ)之間進(jìn)行切換:

  • Default heap:系統(tǒng)未指定堆時(shí)
  • App heap:應(yīng)用在其中分配內(nèi)存的主堆
  • Image heap:系統(tǒng)啟動(dòng)映像,包含啟動(dòng)期間預(yù)加載的類。 此處的分配保證絕不會(huì)移動(dòng)或消失
  • Zygote heap:寫時(shí)復(fù)制堆,其中的應(yīng)用進(jìn)程是從 Android 系統(tǒng)中派生的

Instance View 中,每個(gè)實(shí)例都包含以下信息:

  • Depth:從任意 GC 根到所選實(shí)例的最短 hop 數(shù)
  • Shallow Size:此實(shí)例的大小
  • Retained Size:此實(shí)例支配的內(nèi)存大小

分析內(nèi)存的技巧

使用 Memory Profiler 時(shí),你可以應(yīng)用代碼施加壓力并嘗試強(qiáng)制內(nèi)存泄漏。 在應(yīng)用中引發(fā)內(nèi)存泄漏的一種方式是,先讓其運(yùn)行一段時(shí)間,然后再檢查堆。 泄漏在堆中可能逐漸匯聚到分配頂部。 不過,泄漏越小,你越需要運(yùn)行更長時(shí)間的應(yīng)用才能看到泄漏

您還可以通過以下方式之一觸發(fā)內(nèi)存泄漏:

  • 將設(shè)備從縱向旋轉(zhuǎn)為橫向,然后在不同的 Activity 狀態(tài)下反復(fù)操作多次。 旋轉(zhuǎn)設(shè)備經(jīng)常會(huì)導(dǎo)致應(yīng)用泄漏 ActivityContextView 對(duì)象,因?yàn)橄到y(tǒng)會(huì)重新創(chuàng)建 Activity,而如果您的應(yīng)用在其他地方保持對(duì)這些對(duì)象之一的引用,系統(tǒng)將無法對(duì)其進(jìn)行垃圾回收
  • 處于不同的 Activity 狀態(tài)時(shí),在您的應(yīng)用與另一個(gè)應(yīng)用之間切換(導(dǎo)航到主屏幕,然后返回到您的應(yīng)用)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372