喜歡我的文章,歡迎關注微信公眾號「軟體測試藝術」,一起學習提高。

1、 內存泄露

Android系統為每一個運行的程序都指定了一個最大運行內存,超過這個值則會觸發OOM機制,反應在界面就是閃退、 Crash現象,導致OOM發生的原因比如內存泄露或者是代碼不考慮後果使用大量的資源,都有可能導致OOM出現的。OOM的臨界值可以通過adb shell getprop | findstr 「heap」查看到:

2、 Android的GC機制

Android GC機制沿用了java的GC機制,當需要新內存去分配對象的時候而剩餘不夠的時候,會觸發GC,把無用的對象回收掉,其中一個重要的演算法便是分代式演算法,這個演算法把虛擬機分為年輕代、老年代和持久代,對象先分配到年輕代,然後GC多次後還存活的將會移動到老年代,老年代就不會頻繁觸發GC機制,一般觸發頻繁的都是年輕代的對象。

3、 為什麼會內存泄露

上面我們知道了GC機制,那麼如果GC過後程序還是沒有內存,那麼會發生OOM,導致GC後還是沒有足夠內存分配新對象的主要原因就是內存泄露了。首先要知道內存泄露也就是GC不掉的根源是生命周期長的對象持有生命周期短的對象,導致無用的對象一直無法回收。以下是幾個典型的分類:

1)**靜態類相關的泄露:**static對象的生命周期伴隨著整個程序的生命周期,所以這塊要注意不要把一些對象引用添加到static對象裡面去,會造成與之關聯的對象無法回收。

2)各種資源的釋放:包括cursor的關閉,IO流的關閉,bitmap的回收等,進行一些帶有緩存的資源一定要關閉或者釋放。

3)Handler的泄露:調用handler的delay的時候,會被認為對象是有用的,導致無法回收,還有handler開啟線程去下載東西沒有下載完成的時候,也會因為線程導致無法回收activity;或者使用handlerThread的時候,有延遲的方法,都會導致無法回收。其主要原因在於handler是持有activity的引用,主線程不是自帶一個Looper然後給handler用,導致有關聯關係。

4)各種註冊引用方法:比如一個常駐的後台線程處理某些時間,把當前對象註冊,因為一直持有對象引用,導致這個activity一直保留,所以不用的時候需要反註冊。

5)把對象緩存進容器內卻忘記remove掉:有時候為了加快頁面響應,結果緩存一些對象到容器內,結果越加越多,然後掛掉。

4、 系統級別的內存管理

1)LMK機制和oom_adj的值

Android內核有個專用的驅動low-memory-kill,當系統級別的內存不夠的時候會根據oom_adj的值以及內存分配狀況去kill掉某個進程,oom_adj可以在/proc/[pid]/oom_adj看到,並且這個值會隨著進程的狀態改變而改變,比如系統進程一般是-16,越大越容易被幹掉。

2)5個進程的優先順序

前台進程:當前運行的,基本不死 ;

可見進程:界面可以見到,比如被遮擋 ;

服務進程:進程帶後台服務的,比如播放器 ;

後台進程:點擊home鍵,但不退出,就是後台進程了,有比較大幾率會被殺;

空進程:退出應用程序,還在後台保留這空進程,為的是加快啟動速率,最優先。

5、 內存抖動

內存抖動是指內存頻繁地分配和回收,而頻繁的GC會導致卡頓,嚴重時還會導致OOM(主要原因還是有因為大量小的對象頻繁創建,導致內存碎片,從而當需要分配內存時,雖然總體上還是有剩餘內存可分配,而由於這些內存不連續,導致無法分配,系統直接就返回OOM了)

6、 內存名詞VSS、RSS、PSS、USS解釋

VSS - Virtual Set Size 虛擬耗用內存(包含共享庫佔用的內存)

RSS - Resident Set Size 實際使用物理內存(包含共享庫佔用的內存)

PSS - Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存)

USS - Unique Set Size 進程獨自佔用的物理內存(不包含共享庫佔用的內存)

大小規律:

一般來說內存佔用大小有如下規律:VSS >= RSS >= PSS >= USS

7、 內存值獲取方法

使用命令 adb shell dumpsys meminfo package_name 獲取內存信息,如日曆的內存信息如下:

PSS Total:進程各部分內存的消耗,是所有進程PSS相加得到系統佔用內存的總和

Native Heap:Native代碼分配的內存,虛擬機和Android框架分配內存。關於什麼是Native代碼,即非Java代碼分配的內存

Dalvik Heap:Java對象分配的佔據內存

Dalvik Other:類數據結構和索引佔據內存

Stack:棧內存

Private Dirty:它基本上是進程內不能被分頁到磁碟的內存,也不和其他進程共享,private Dirty內存是最重要的部分,因為只被自己進程使用

Private Clean:是已經映射持久文件使用的內存頁(例如正在被執行的代碼),因此一段時間不使用的話就可以置換出去

Heap Alloc:是Dalvik堆和本地堆分配使用的大小,它的值比Pss Total和Private Dirty大,因為進程是從Zygote中複製分裂出來的,包含了進程共享的分配部分

Ashmem:不以dalvik-開頭的內存區域,匿名共享內存用來提供共享內存通過分配一個多個進程,Android匿名共享內存是基於Linux共享內存的,都是在tmpfs文件系統上新建文件,並將其映射到不同的進程空間,從而達到共享內存的目的,只是,Android在Linux的基礎上進行了改造,並藉助Binder+fd文件描述符實現了共享內存的傳遞。

Other dev:內部driver佔用的內存

.so mmap:C 庫代碼佔用的內存

.jar mmap:Java 文件代碼佔用的內存

.apk mmap:apk代碼佔用的內存

.ttf mmap:ttf 文件代碼佔用的內存

.dex mmap:Dex 文件代碼佔用的內存

Other mmap:其他文件佔用的內存

8、 測試場景選擇

內存出現泄漏的前提條件一定是有新的內存分配,所以測試場景會選擇有新對象創建的場景,並結合用戶的使用場景和頻率來確定優先順序。測試場景主要有以下三種情況,配合測試次數,然後可以每5次獲取一次內存值進行判斷,一般測試300次,如果各種內存測試完成並等待5分鐘後內存沒有釋放,則高概率存在內存泄露:

1)新畫面打開

由於新的畫面打開,就會創建新的Activity和View,並有許多其他對象被創建。

測試方法:

反覆進入退出需要測試的目標Activity,如果發現Activities和Views的一直在增長,則內存泄露一定發生(退出時如果手動GC,則Activities和Views的數量應該為0)

2)畫面旋轉

當屏幕旋轉時,Orientation設置發生了改變,當前顯示的Activity會被重新創建。

測試方法:進入需要測試的目標Activity,反覆橫豎屏切換,如果發現Activities數量等其他值一直在增長,則內存泄露一定發生

3)滑動屏幕

滑動屏幕會使屏幕中顯示的對象(比如瀏覽器小說閱讀內容)創建。

測試方法:進入需要測試的目標Activity,一直固定某個方向滑動(向左),如果發現內存值一直在增長,則內存泄露一定發生

Case例子,僅供參考:

測試過程中的值記錄模板,僅供參考:

注意:

1)每個應用的腳本需要獲取的信息可以直接涉及好關聯應用或進程的數據值,例如測試camera時後台camera服務進程,多媒體進程、相冊進程。

2)針對內存泄露的測試,需要開發自動化腳本測試,然後測試過程中獲取測試的值存入execl的固定模板,測試完成後根據測試結果數據判斷是否有內存泄露

9、 定位內存泄露的原因(如果是真機測試,安裝一個debug版本的apk,否則monitor無法顯示進程)

方法一:使用DDMS(Monitor)檢測內存泄露--需要

步驟2、然後在打開DDMS, 選擇Heap標籤,然後點擊Cause GC按鈕,點擊Cause GC是手動觸發JAVA垃圾回收器,如下圖:

如果我們要測試某個Activity是否發生內存泄露,我們可以反覆進入和退出這個Activity, 再手動觸發幾次垃圾回收,觀察上圖中 data object這一欄中的 Total Size的大小是保持穩定還是有明顯的變大趨勢,如果有明顯的變大趨勢就說明這個Activity存在內存泄露的問題,需要在具體分析。

推薦閱讀:

相关文章