Android App內(nèi)存泄露測(cè)試方法總結(jié)

和你一起終身學(xué)習(xí),這里是程序員Android
經(jīng)典好文推薦,通過(guò)閱讀本文,您將收獲以下知識(shí)點(diǎn):
一、內(nèi)存泄露
二、 Android的GC機(jī)制
三、為什么會(huì)內(nèi)存泄露
四、 系統(tǒng)級(jí)別的內(nèi)存管理
五、內(nèi)存抖動(dòng)
六、內(nèi)存名詞VSS、RSS、PSS、USS解釋
七、 內(nèi)存值獲取方法
八、 測(cè)試場(chǎng)景選擇
九、 定位內(nèi)存泄露的原因
一、內(nèi)存泄露
Android系統(tǒng)為每一個(gè)運(yùn)行的程序都指定了一個(gè)最大運(yùn)行內(nèi)存,超過(guò)這個(gè)值則會(huì)觸發(fā)OOM機(jī)制,反應(yīng)在界面就是閃退、 Crash現(xiàn)象,導(dǎo)致OOM發(fā)生的原因比如內(nèi)存泄露或者是代碼不考慮后果使用大量的資源,都有可能導(dǎo)致OOM出現(xiàn)的。OOM的臨界值可以通過(guò)adb shell getprop | findstr “heap”查看到:

image.png
二、 Android的GC機(jī)制
Android GC機(jī)制沿用了java的GC機(jī)制,當(dāng)需要新內(nèi)存去分配對(duì)象的時(shí)候而剩余不夠的時(shí)候,會(huì)觸發(fā)GC,把無(wú)用的對(duì)象回收掉,其中一個(gè)重要的算法便是分代式算法,這個(gè)算法把虛擬機(jī)分為年輕代、老年代和持久代,對(duì)象先分配到年輕代,然后GC多次后還存活的將會(huì)移動(dòng)到老年代,老年代就不會(huì)頻繁觸發(fā)GC機(jī)制,一般觸發(fā)頻繁的都是年輕代的對(duì)象。
三、為什么會(huì)內(nèi)存泄露
上面我們知道了GC機(jī)制,那么如果GC過(guò)后程序還是沒(méi)有內(nèi)存,那么會(huì)發(fā)生OOM,導(dǎo)致GC后還是沒(méi)有足夠內(nèi)存分配新對(duì)象的主要原因就是內(nèi)存泄露了。首先要知道內(nèi)存泄露也就是GC不掉的根源是生命周期長(zhǎng)的對(duì)象持有生命周期短的對(duì)象,導(dǎo)致無(wú)用的對(duì)象一直無(wú)法回收。以下是幾個(gè)典型的分類(lèi):
1) 靜態(tài)類(lèi)相關(guān)的泄露:
static對(duì)象的生命周期伴隨著整個(gè)程序的生命周期,所以這塊要注意不要把一些對(duì)象引用添加到static對(duì)象里面去,會(huì)造成與之關(guān)聯(lián)的對(duì)象無(wú)法回收。
2)各種資源的釋放:
包括cursor的關(guān)閉,IO流的關(guān)閉,bitmap的回收等,進(jìn)行一些帶有緩存的資源一定要關(guān)閉或者釋放。
3)Handler的泄露:
調(diào)用handler的delay的時(shí)候,會(huì)被認(rèn)為對(duì)象是有用的,導(dǎo)致無(wú)法回收,還有handler開(kāi)啟線(xiàn)程去下載東西沒(méi)有下載完成的時(shí)候,也會(huì)因?yàn)榫€(xiàn)程導(dǎo)致無(wú)法回收activity;或者使用handlerThread的時(shí)候,有延遲的方法,都會(huì)導(dǎo)致無(wú)法回收。其主要原因在于handler是持有activity的引用,主線(xiàn)程不是自帶一個(gè)Looper然后給handler用,導(dǎo)致有關(guān)聯(lián)關(guān)系。
4)各種注冊(cè)引用方法:
比如一個(gè)常駐的后臺(tái)線(xiàn)程處理某些時(shí)間,把當(dāng)前對(duì)象注冊(cè),因?yàn)橐恢背钟袑?duì)象引用,導(dǎo)致這個(gè)activity一直保留,所以不用的時(shí)候需要反注冊(cè)。
5)把對(duì)象緩存進(jìn)容器內(nèi)卻忘記remove掉:
有時(shí)候?yàn)榱思涌祉?yè)面響應(yīng),結(jié)果緩存一些對(duì)象到容器內(nèi),結(jié)果越加越多,然后掛掉。
四、 系統(tǒng)級(jí)別的內(nèi)存管理
1)LMK機(jī)制和oom_adj的值
Android內(nèi)核有個(gè)專(zhuān)用的驅(qū)動(dòng)low-memory-kill,當(dāng)系統(tǒng)級(jí)別的內(nèi)存不夠的時(shí)候會(huì)根據(jù)oom_adj的值以及內(nèi)存分配狀況去kill掉某個(gè)進(jìn)程,oom_adj可以在/proc/[pid]/oom_adj看到,并且這個(gè)值會(huì)隨著進(jìn)程的狀態(tài)改變而改變,比如系統(tǒng)進(jìn)程一般是-16,越大越容易被干掉。
2)5個(gè)進(jìn)程的優(yōu)先級(jí)
前臺(tái)進(jìn)程:當(dāng)前運(yùn)行的,基本不死 ;
可見(jiàn)進(jìn)程:界面可以見(jiàn)到,比如被遮擋 ;
服務(wù)進(jìn)程:進(jìn)程帶后臺(tái)服務(wù)的,比如播放器 ;
后臺(tái)進(jìn)程:點(diǎn)擊home鍵,但不退出,就是后臺(tái)進(jìn)程了,有比較大幾率會(huì)被殺;
空進(jìn)程:退出應(yīng)用程序,還在后臺(tái)保留這空進(jìn)程,為的是加快啟動(dòng)速率,最優(yōu)先。
五、內(nèi)存抖動(dòng)
內(nèi)存抖動(dòng)是指內(nèi)存頻繁地分配和回收,而頻繁的GC會(huì)導(dǎo)致卡頓,嚴(yán)重時(shí)還會(huì)導(dǎo)致OOM(主要原因還是有因?yàn)榇罅啃〉膶?duì)象頻繁創(chuàng)建,導(dǎo)致內(nèi)存碎片,從而當(dāng)需要分配內(nèi)存時(shí),雖然總體上還是有剩余內(nèi)存可分配,而由于這些內(nèi)存不連續(xù),導(dǎo)致無(wú)法分配,系統(tǒng)直接就返回OOM了)
六、內(nèi)存名詞VSS、RSS、PSS、USS解釋
VSS - Virtual Set Size 虛擬耗用內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
RSS - Resident Set Size 實(shí)際使用物理內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
PSS - Proportional Set Size 實(shí)際使用的物理內(nèi)存(比例分配共享庫(kù)占用的內(nèi)存)
USS - Unique Set Size 進(jìn)程獨(dú)自占用的物理內(nèi)存(不包含共享庫(kù)占用的內(nèi)存)
大小規(guī)律:
一般來(lái)說(shuō)內(nèi)存占用大小有如下規(guī)律:VSS >= RSS >= PSS >= USS

image.png
七、 內(nèi)存值獲取方法
使用命令 adb shell dumpsys meminfo package_name 獲取內(nèi)存信息,如日歷的內(nèi)存信息如下:

image.png
PSS Total:進(jìn)程各部分內(nèi)存的消耗,是所有進(jìn)程PSS相加得到系統(tǒng)占用內(nèi)存的總和
Native Heap:Native代碼分配的內(nèi)存,虛擬機(jī)和Android框架分配內(nèi)存。關(guān)于什么是Native代碼,即非Java代碼分配的內(nèi)存
Dalvik Heap:Java對(duì)象分配的占據(jù)內(nèi)存
Dalvik Other:類(lèi)數(shù)據(jù)結(jié)構(gòu)和索引占據(jù)內(nèi)存
Stack:棧內(nèi)存
Private Dirty:它基本上是進(jìn)程內(nèi)不能被分頁(yè)到磁盤(pán)的內(nèi)存,也不和其他進(jìn)程共享,private Dirty內(nèi)存是最重要的部分,因?yàn)橹槐蛔约哼M(jìn)程使用
Private Clean:是已經(jīng)映射持久文件使用的內(nèi)存頁(yè)(例如正在被執(zhí)行的代碼),因此一段時(shí)間不使用的話(huà)就可以置換出去
Heap Alloc:是Dalvik堆和本地堆分配使用的大小,它的值比Pss Total和Private Dirty大,因?yàn)檫M(jìn)程是從Zygote中復(fù)制分裂出來(lái)的,包含了進(jìn)程共享的分配部分
Ashmem:不以dalvik-開(kāi)頭的內(nèi)存區(qū)域,匿名共享內(nèi)存用來(lái)提供共享內(nèi)存通過(guò)分配一個(gè)多個(gè)進(jìn)程,Android匿名共享內(nèi)存是基于Linux共享內(nèi)存的,都是在tmpfs文件系統(tǒng)上新建文件,并將其映射到不同的進(jìn)程空間,從而達(dá)到共享內(nèi)存的目的,只是,Android在Linux的基礎(chǔ)上進(jìn)行了改造,并借助Binder+fd文件描述符實(shí)現(xiàn)了共享內(nèi)存的傳遞。
Other dev:內(nèi)部driver占用的內(nèi)存
.so mmap:C 庫(kù)代碼占用的內(nèi)存
.jar mmap:Java 文件代碼占用的內(nèi)存
.apk mmap:apk代碼占用的內(nèi)存
.ttf mmap:ttf 文件代碼占用的內(nèi)存
.dex mmap:Dex 文件代碼占用的內(nèi)存
Other mmap:其他文件占用的內(nèi)存
八、 測(cè)試場(chǎng)景選擇
內(nèi)存出現(xiàn)泄漏的前提條件一定是有新的內(nèi)存分配,所以測(cè)試場(chǎng)景會(huì)選擇有新對(duì)象創(chuàng)建的場(chǎng)景,并結(jié)合用戶(hù)的使用場(chǎng)景和頻率來(lái)確定優(yōu)先級(jí)。測(cè)試場(chǎng)景主要有以下三種情況,配合測(cè)試次數(shù),然后可以每5次獲取一次內(nèi)存值進(jìn)行判斷,一般測(cè)試300次,如果各種內(nèi)存測(cè)試完成并等待5分鐘后內(nèi)存沒(méi)有釋放,則高概率存在內(nèi)存泄露:
1)新畫(huà)面打開(kāi)
由于新的畫(huà)面打開(kāi),就會(huì)創(chuàng)建新的Activity和View,并有許多其他對(duì)象被創(chuàng)建。
測(cè)試方法:
反復(fù)進(jìn)入退出需要測(cè)試的目標(biāo)Activity,如果發(fā)現(xiàn)Activities和Views的一直在增長(zhǎng),則內(nèi)存泄露一定發(fā)生(退出時(shí)如果手動(dòng)GC,則Activities和Views的數(shù)量應(yīng)該為0)
2)畫(huà)面旋轉(zhuǎn)
當(dāng)屏幕旋轉(zhuǎn)時(shí),Orientation設(shè)置發(fā)生了改變,當(dāng)前顯示的Activity會(huì)被重新創(chuàng)建。
測(cè)試方法:進(jìn)入需要測(cè)試的目標(biāo)Activity,反復(fù)橫豎屏切換,如果發(fā)現(xiàn)Activities數(shù)量等其他值一直在增長(zhǎng),則內(nèi)存泄露一定發(fā)生
3)滑動(dòng)屏幕
滑動(dòng)屏幕會(huì)使屏幕中顯示的對(duì)象(比如瀏覽器小說(shuō)閱讀內(nèi)容)創(chuàng)建。
測(cè)試方法:進(jìn)入需要測(cè)試的目標(biāo)Activity,一直固定某個(gè)方向滑動(dòng)(向左),如果發(fā)現(xiàn)內(nèi)存值一直在增長(zhǎng),則內(nèi)存泄露一定發(fā)生
Case例子,僅供參考:

image.png
測(cè)試過(guò)程中的值記錄模板,僅供參考:

image.png
注意:
1)每個(gè)應(yīng)用的腳本需要獲取的信息可以直接涉及好關(guān)聯(lián)應(yīng)用或進(jìn)程的數(shù)據(jù)值,例如測(cè)試camera時(shí)后臺(tái)camera服務(wù)進(jìn)程,多媒體進(jìn)程、相冊(cè)進(jìn)程。
2)針對(duì)內(nèi)存泄露的測(cè)試,需要開(kāi)發(fā)自動(dòng)化腳本測(cè)試,然后測(cè)試過(guò)程中獲取測(cè)試的值存入execl的固定模板,測(cè)試完成后根據(jù)測(cè)試結(jié)果數(shù)據(jù)判斷是否有內(nèi)存泄露
九、 定位內(nèi)存泄露的原因
如果是真機(jī)測(cè)試,安裝一個(gè)debug版本的apk,否則monitor無(wú)法顯示進(jìn)程
方法一:使用DDMS(Monitor)檢測(cè)內(nèi)存泄露--需要

image.png
步驟2、然后在打開(kāi)DDMS, 選擇Heap標(biāo)簽,然后點(diǎn)擊Cause GC按鈕,點(diǎn)擊Cause GC是手動(dòng)觸發(fā)JAVA垃圾回收器,如下圖:

image.png
如果我們要測(cè)試某個(gè)Activity是否發(fā)生內(nèi)存泄露,我們可以反復(fù)進(jìn)入和退出這個(gè)Activity, 再手動(dòng)觸發(fā)幾次垃圾回收,觀(guān)察上圖中 data object這一欄中的 Total Size的大小是保持穩(wěn)定還是有明顯的變大趨勢(shì),如果有明顯的變大趨勢(shì)就說(shuō)明這個(gè)Activity存在內(nèi)存泄露的問(wèn)題,需要在具體分析。
參考鏈接:https://zhuanlan.zhihu.com/p/68351554
友情推薦:
至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺(jué)得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請(qǐng)聯(lián)系小編刪除,歡迎您的建議與指正。同時(shí)期待您的關(guān)注,感謝您的閱讀,謝謝!
點(diǎn)個(gè)在看,方便您使用時(shí)快速查找!
