Memory Profiler是Android Profiler中的一個組件芜果,Android Profiler是Android Studio3.0用來替換之前Android Monitor的觀察工具豆胸,主要用來觀察內(nèi)存偎快,網(wǎng)絡(luò),cpu溫度辆琅。今天著重介紹其中的Memory Profiler笙什。它能夠讓你識別出來內(nèi)存泄漏和內(nèi)存抖動即供,導(dǎo)致應(yīng)用卡頓吨悍,anr和crash. 它可以給你展示一個內(nèi)存使用的真實圖表扫茅,讓你知道當時內(nèi)存使用情況,還能強制內(nèi)存回收育瓜,和跟蹤內(nèi)存分配.
如何打開Memory Profiler葫隙?
最后進入Memory Profiler
為什么要去觀察應(yīng)用內(nèi)存的使用情況?
剛才也提到了Memory Profiler是用來解決內(nèi)存分配中產(chǎn)生抖動躏仇,導(dǎo)致應(yīng)用卡頓恋脚,anr和crash問題. 在Android系統(tǒng)內(nèi)存管理上,它是提供一套內(nèi)存回收機制去回收無用的對象钙态,其實就是Dalvik虛擬機的垃圾回收器,當垃圾回收器啟動回收機制的時候菇晃,其實會對應(yīng)用的運行產(chǎn)生一點影響册倒,但是這種影響來說一般微乎其微,察覺不到磺送。但是如果你的內(nèi)存分配比垃圾回收快很多驻子,這種情況可能導(dǎo)致垃圾回收器回收內(nèi)存不及時,從而導(dǎo)致應(yīng)用出現(xiàn)卡頓的現(xiàn)象.(這其實就是內(nèi)存抖動所產(chǎn)生的影響). 另外一個問題就是內(nèi)存泄漏估灿,內(nèi)存的持續(xù)泄漏可能導(dǎo)致內(nèi)存溢出崇呵,從而app運行出現(xiàn)outofmem異常。
Memory Profiler通過以下方面防治上面出現(xiàn)的問題:
1馅袁,觀察不必要的內(nèi)存分配域慷。(這種內(nèi)存分配導(dǎo)致效率降低)
2,Dump the Java heap 去觀察指定時間對象的在內(nèi)存中的分配情況,若干次Dump能夠幫助你發(fā)現(xiàn)內(nèi)存泄漏
3犹褒,測試極端的用戶交互情況下的內(nèi)存分配(比如說狂點某個請求按鈕)抵窒,看看內(nèi)存使用情況如何,是否出現(xiàn)內(nèi)存抖動.
Memory Profiler主面板介紹
1:強制內(nèi)存回收按鈕
2:Dump the Java heap
3:開始/停止記錄內(nèi)存分配情況
4:縮械铩/放大時間線
5,實時播放內(nèi)存分配情況(這個按鈕點下試試便清楚了)
6,發(fā)生一些事件的記錄(如Activity的跳轉(zhuǎn)李皇,事件的輸入,屏幕的旋轉(zhuǎn))
7,內(nèi)存使用時間線
包含多少內(nèi)存被使用(左邊的y軸)宙枷,還有頂上的顏色標記內(nèi)存的類型掉房,右邊的y軸表明分配對象的個數(shù),另外出現(xiàn)垃圾回收事件會有一個小圖標.
關(guān)于頂部的幾種內(nèi)存類型介紹:
Java : java代碼分配的內(nèi)存
Native:c/c++代碼分配的內(nèi)存(有時候其實并沒有使用到c/c++代碼,但還是會有Native的內(nèi)存分配,因為Android Framework會去通過java代碼訪問一些需要使用Native的資源慰丛,如圖像資源Bitmap)
Graphics:圖像緩存等卓囚,包括GL surfaces, GL textures等.
Stack:棧內(nèi)存(包括java和c/c++)
Code:代碼的內(nèi)存分配(例如代碼,資源璧帝,libs等等)
Other:這個是連系統(tǒng)都不知道是什么類型的內(nèi)存捍岳,放在這里.
Allocated: jave分配的對象個數(shù) (在Android7.1和以下的設(shè)備,這個計數(shù)是在設(shè)備連接后開始睬隶,所以并不是app啟動時候的計數(shù)锣夹。Android8.0或更高,在系統(tǒng)里面內(nèi)置的Profiler工具苏潜,所以無論什么時候連接银萍,都是app啟動時候的計數(shù))
如何觀察對象分配的情況?
我們需要關(guān)注如下信息:
1,什么類型對象被分配恤左,分配了多大的空間
2,對象分配的棧調(diào)用贴唇,是在哪個線程中調(diào)用的
3,對象的釋放時間(只針對8.0+)
如果是8.0以上的設(shè)備,不需要點擊之前面板介紹中的按鈕3飞袋,就可以觀察某一段時間的內(nèi)存分配情況戳气,如果是7.1或以下是需要點擊按鈕3開始和停止。
Android8.0觀察一段時間的內(nèi)存分配情況:
Android7.1或以下觀察一段時間的內(nèi)存分配情況:
在7.1以下設(shè)備和8.0在記錄對象個數(shù)上面也是有區(qū)別的巧鸭,前者記錄是最大65535個最近使用的對象瓶您,8.0卻沒有這個限制.
當分析結(jié)束后會彈出如下面板:
下面是重頭戲,查看對象分配情況纲仍,也就是我們前面提到需要關(guān)注什么類型對象被分配呀袱,分配了多大的空間。
1,在Class Name列看一下有沒有異常分配的對象郑叠,個數(shù)很多夜赵,占用內(nèi)存比較大。點擊頭部Class Name進行一個按字母排序操作乡革,點擊Class Name面板下面的類名可以看到Instance View面板詳細的對象信息.
2,點擊Instance View面板中的對象寇僧,可以看到調(diào)用棧信息和調(diào)用的線程.
3,在Call Stack中點擊可以跳轉(zhuǎn)到實際的代碼.
以上是捕獲一段時間的內(nèi)存分配情況摊腋,如果想捕獲一瞬間的內(nèi)存分配需要用到heap dump.
捕獲一個heap dump
捕獲一個heap dump觀察某一個時間點的對象分配情況,注意之前介紹是一個時間段婉宰,而這里是時間點歌豺。它有助于幫助我們分析內(nèi)存泄漏,比如當我應(yīng)用使用一段時候后心包,捕獲了一個heap dump类咧,這個heap dump里面發(fā)現(xiàn)了并不應(yīng)該存在的對象分配情況,這說明是存在內(nèi)存泄漏的蟹腾。通過一個heap dump你可以看到以下內(nèi)容:
1痕惋,你的app分配了什么樣的對象類型,每個類型分配了多少個數(shù)和大小娃殖。
2值戳,使用了多少內(nèi)存
3,每個對象在代碼中的使用位置
4炉爆,對象分配的調(diào)用棧情況
捕獲一個heap dump在工具欄中點擊之前面板介紹中的按鈕2堕虹,稍等一會兒便能夠看到類似于之前記錄內(nèi)存分配后的面板彈出。
在上面圖片中可以看到如下列:
Class Name : 這個很好理解芬首,就是類名
Alloc Count : 對象個數(shù)
Native Size : c/c++層內(nèi)存大小(bytes)
Shallow Size : java層內(nèi)存大小(bytes)
Retained Size : 這個是這個類中所引用到的對象的總大小 * 該類對象的個數(shù)
當點擊app heap下拉列表會出現(xiàn)3個選項
Default heap: 這個我也不太明白是什么意思
App heap: app中的堆分配
Image heap: 圖像的堆分配
Zygote heap: 這個按照官方的解釋是來自安卓系統(tǒng)fork進程的地方產(chǎn)生的寫數(shù)據(jù)備份
當點擊Arrange by class下拉列表會出現(xiàn)3個選項
Arrange by class:根據(jù)類名進行分組
Arrange by package:根據(jù)包名進行分組
Arrange by callstack:根據(jù)調(diào)用棧進行分配(這個目前也不是太理解)
當我們點擊其中一個類的時候會彈出一個新的Instance View面板,如下圖:
每列中包括以下:
Depth: GC root到達該實例的最短跳數(shù).
Native Size: c/c++層中內(nèi)存的大小(bytes)
Shallow Size:java層內(nèi)存大小(bytes)
Retained Size:這個類中所引用到的對象的總大小(bytes)
另外補充一下赴捞,heap dump是看不到調(diào)用棧信息的.也就是上圖中的Call Stack面板.
分析你的heap,按照一下步驟.
1,瀏覽Class Name列表,看看有沒有大量對象存在,并且這些對象你認為是不應(yīng)該存在的郁稍,可能存在內(nèi)存泄漏的情況. 點擊類名可以看到詳細的對象信息.
2,在這個Instance View面板中赦政,點擊一個實例References面板就會顯示出來,里面都是使用該Instance的Reference耀怜,點擊剪頭可以看到引用它的所有區(qū)域恢着。點擊鼠標右鍵可以選擇go to instance去看到引用該引用的引用,或者jump to source去看調(diào)用的源代碼.
另外heap dump也是可以保存成為HPROF文件的,點擊如下按鈕即可保存起來财破,用于以后分析掰派,或用作其它工具分析.
一般出現(xiàn)內(nèi)存泄漏的原因有:
1,長期引用到Activity,Context,View,Drawable的對象左痢。
2靡羡,非靜態(tài)的內(nèi)部類,例如Runnable它可以引用到Activity的實例
3抖锥,一些長期緩存對象
下面舉一個例子具體分析一下如何使用Memory Profiler查找內(nèi)存泄漏.
查找內(nèi)存泄漏有以下幾個方式
1,一般我排查內(nèi)存泄漏的方式是亿眠,啟動應(yīng)用碎罚,看一下當前內(nèi)存使用了多少磅废,使用應(yīng)用一段時間后(當然你不想親自動手點來點去,也可以使用monkey進行一次自動化測試), 退回到應(yīng)用首頁荆烈,看看當前內(nèi)存又是多少拯勉。進行一次heap dump, 看看結(jié)果竟趾,分析一下有沒有可疑的對象分配(比如說大量重復(fù)的Activity,同一個類型對象比較多宫峦,對象內(nèi)存占用較大).
2,發(fā)現(xiàn)可疑點后岔帽,通過分析結(jié)果,可以找到相應(yīng)代碼导绷,找到代碼當然也能找到使用代碼的場景犀勒,例如是Activity泄漏,反復(fù)進行畫面的跳轉(zhuǎn)(如果你的應(yīng)用支持橫豎平切換的話妥曲,也可以反復(fù)旋轉(zhuǎn)屏幕)贾费,然后強制gc回收,看看內(nèi)存是否存在只增不減的情況.
3,也可以使用allocation跟蹤一段時間內(nèi)存分配情況檐盟,拿出來做分析
4,最后推薦一款leakcanary工具使用(具體可看:https://github.com/square/leakcanary)
當然如果時間允許的話褂萧,以上每個步驟都需要進行詳細測試。
下面開始正式的栗子:
我啟動了一個疑似存在內(nèi)存泄漏app,然后使用了一段時間葵萎,進行了一次heap dump, 結(jié)果如下
很明顯我發(fā)現(xiàn)了一個可疑的類OutOfMemActivity, 它存在多個實例导犹,實際上在已知該app業(yè)務(wù)邏輯中是不應(yīng)該會有這么多OutOfMemActivity實例的,于是我變點開它的Instance View.
可疑點如紅色剪頭所指羡忘,因為外部類實例引用到Activity都是不正常的操作谎痢,這里Broadcast的實例引用到了Activity.
點擊跳轉(zhuǎn)到源碼,發(fā)現(xiàn)是內(nèi)部類引用到外部類實例(Activity)的情況導(dǎo)致了內(nèi)存泄漏.
其實引起內(nèi)存泄漏的情況還有很多壳坪,這里就不一一列舉舶得,上面只是展示一下Memory Profiler這個工具的使用.