您好,登錄后才能下訂單哦!
這篇文章給大家介紹App的待機內存增長的原因是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
在上一節里,我們介紹了內存測試的基本流程,講述了如何發現并處理簡單的內存問題。對于Dalvik Heap部分總結出了一些常見的問題模式,以及如何使用工具識別和處理這些常見的內存問題。
當簡單問題不再是問題的時候,我們就會開始遇上一些奇怪問題了,類似于下面這些:
“我們這個版本引入了一個挺簡單的庫,內存就漲了2M” “這些代碼只是初始化了幾個對象,還沒有開始用呢” “我只是改了一行代碼,沒有創建新對象” “我一行代碼都沒改,怎么會漲呢”
這次出現的問題就是這樣這一類問題,新版本的Dalvik Heap Pss內存出現了2M左右的增長。但Dalvik Heap Alloc只增長了273K的情況下。而從Dalvik Heap Free也能看出大部分增長的內存是空閑狀態的。
經過一段時間對問題的觀察,我們有以下幾點發現:
經過較長時間待機后也沒有被釋放回系統。
有幾處代碼會導致內存增長,只要將這些代碼屏蔽掉,內存情況就下降到正常水品。
這些代碼分配的內存并不多,甚至有些地方是不需要分配內存的。
有些代碼并不是這個版本新加入的,已經存在較長時間了。
使用裁剪功能的方法編譯并分析內存后,基本可以確定是新加入代碼消耗了內存,但并沒有內存泄漏,代碼經過review也沒有發現問題。
這個結果讓我們陷入了困惑,常用的方法找不出問題,說明有更深層次的原因。接下來要從更底層的Dalvik虛擬機尋找問題。
為了弄清楚為什么DVM占著內存不釋放,我們閱讀了DVM分配內存部分的代碼。位置在Android源碼的dalvik/vm/alloc下,約255K。分析出的主要流程如下:
1) DVM使用mmap系統調用從系統分配大塊內存作為Java Heap。根據系統機制,如果分類的內存尚未真正使用,就不計入PrivateDirty和PSS。例如圖1-8,Heap Size/Alloc很多,但大部分是共享,實際使用的較少。所以反映到PrivateDirty/PSS里的內存并不多。
圖1-8 共享內存較多的進程
2) New對象之后,由于要向對應的地址寫入數據,內核開始真正分配該地址對應的4K物理內存頁面。
Alloc.cpp, 176行起:
3) 運行一段時間后,開始GC,有些對象被回收了,有些會一直存在。
4) 在GC時,有可能會進行trim。即將空閑的物理頁面釋放回系統,表現為PrivateDirty/PSS下降。
HeapSource.cpp, 431行:
在了解DVM分配釋放內存的機制后,根據dumpsys觀察到的現象,猜測可能出現了頁利用率問題(頁內碎片)。如圖1-13所示,第一行:在開始階段,內存分配的較滿。第二行:經過GC后,大部分對象被釋放,少部分留下來。
這種情況下可能會產生的問題是,整頁的4K內存中可能只有一個小對象,但統計PrivateDirty/PSS時還是按4K計算。
在通常的jvm虛擬機中,有Compacting GC機制,整理內存對象,將散布的內存移動到一起。但根據DVM的代碼,DVM的Mark-Sweep算法不能移動對象,即沒有內存整理功能,這種情況下就會形成內存空洞。
在猜測了可能的問題后,需要驗證是否如猜測原因所致,由于MAT的對象實例數據中有地址和大小信息,我們先從MAT中導出數據。
在MAT中列出所有對象實例:list_objects java.*,然后選中所有數據導出為CSV格式,如下所示:
Class Name,Shallow Heap,Retained Heap, class java.lang.Class @ 0x41fdd1e8,16,56, class test.bxi$3 @ 0x432501c8,0,0, class test.aaw$c$1 @ 0x4324fef8,0,0, class test.ds @ 0x4324fc88,8,48, class test.bxh @ 0x4324f438,8,248, class test.bxg @ 0x4324f248,0,0, class test.bxd$1 @ 0x4324f028,0,0,
處理導出的csv文件,按頁面進行統計,取每個對象的地址的高位(&0xfffff000),結果相同的對象處在同一頁面中。最后再按每個頁面所有對象的大小分類統計,做出直方圖如圖1-14所示。
這張圖就是被測應用的頁面利用率分布圖,左邊是利用率低的頁面,右邊是利用率高的頁面。如果發現利用率低的頁面數目增加,說明小對象碎片的數量增加了。
為了能夠找出有問題的代碼,我們將上一步得到的數據繼續處理。取出所有使用不滿2K的頁面的內存塊地址,再使用OQL將地址導入到MAT中,分析地址對應的對象是什么。如圖1-15所示就是將地址重新導入到MAT中得到的對象列表了:
在這里基本就能看出來是哪些對象造成了內存的碎片化,數量比較多的前幾個類的自然嫌疑比較大,可以先對前幾個類的相關代碼進行分析。也可以對這些代碼進行針對性的內存測試,觀察內存情況。
通過對生成這些對象的代碼分析和模擬實驗,我們還原出問題的基本過程:
生成對象過程需要較多的臨時變量。
批量生成過程中,由于還有空閑內存,虛擬機沒有做GC。
完成后才進行GC,清除了所有的零時變量,留下碎片化的內存。
下圖是造成這個問題的類似代碼,執行這段代碼將會在內存中形成很多碎片,造成很高的PSS占用。
private Object result[] = new Object[100]; void foo() { for(int i = 0; i < 100; ++i) { byte[] tmp = new byte[2000]; result[i] = new byte[4]; } }
顯示了類似情況下數組的分配范圍,可見數組中每個成員的內存地址都是不連續的,并且相隔很遠。這種情況下就會消耗很多個物理內存頁面,增加Heap Free,造成例子中的問題。
根據上述的流程,我們搞清楚了造成問題的原因,并且找到了問題代碼。那么應當總結一些經驗,以供借鑒。對于測試人員來說,有以下兩個經驗:
MAT是探索Java堆并發現問題的好幫手,能夠迅速發現常見的圖片和大數組等問題。但僅靠MAT提供的功能也不是萬能的,比如這個問題的數據就隱藏在對象的地址中。
對Android測試經驗來說,可能容易找到的是應用代碼及框架的各種測試經驗和指導,底層以及涉及性能的測試經驗并不太多。這方面可以借鑒Linux系統的測試經驗,了解內核及進程相關的知識,熟悉常用工具。
內存分配的最小單位是頁面,通常為4K。
對于開發人員,以下兩個經驗也許能有幫助:
盡量不要在循環中創建很多臨時變量。
可以將大型的循環拆散,分段或者按需執行。
關于App的待機內存增長的原因是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。