【騰訊優測乾貨分享】如何下降App的待機內存(三)——探索內存增加的緣由

本文來自於騰訊優測公衆號(wxutest),未經做者贊成,請勿轉載,原文地址:https://mp.weixin.qq.com/s/8BiKIt3frq9Yv9KV5FXlGwjava

1.3新問題的進一步挖掘

在上一節裏,咱們介紹了內存測試的基本流程,講述瞭如何發現並處理簡單的內存問題。對於Dalvik Heap部分總結出了一些常見的問題模式,以及如何使用工具識別和處理這些常見的內存問題。算法

當簡單問題再也不是問題的時候,咱們就會開始趕上一些奇怪問題了,相似於下面這些:數組

「咱們這個版本引入了一個挺簡單的庫,內存就漲了2M」 「這些代碼只是初始化了幾個對象,尚未開始用呢」 「我只是改了一行代碼,沒有建立新對象」 「我一行代碼都沒改,怎麼會漲呢」微信

此次出現的問題就是這樣這一類問題,新版本的Dalvik Heap Pss內存出現了2M左右的增加。但Dalvik Heap Alloc只增加了273K的狀況下。而從Dalvik Heap Free也能看出大部分增加的內存是空閒狀態的。併發

通過一段時間對問題的觀察,咱們有如下幾點發現:app

  • 通過較長時間待機後也沒有被釋放回系統。
  • 有幾處代碼會致使內存增加,只要將這些代碼屏蔽掉,內存狀況就降低到正常水品。
  • 這些代碼分配的內存並很少,甚至有些地方是不須要分配內存的。
  • 有些代碼並非這個版本新加入的,已經存在較長時間了。
  • 使用裁剪功能的方法編譯並分析內存後,基本能夠肯定是新加入代碼消耗了內存,但並無內存泄漏,代碼通過review也沒有發現問題。

這個結果讓咱們陷入了困惑,經常使用的方法找不出問題,說明有更深層次的緣由。接下來要從更底層的Dalvik虛擬機尋找問題。框架

1.3.1 Dalvik Heap內部機制

爲了弄清楚爲何DVM佔着內存不釋放,咱們閱讀了DVM分配內存部分的代碼。位置在Android源碼的dalvik/vm/alloc下,約255K。分析出的主要流程以下:jvm

1) DVM使用mmap系統調用從系統分配大塊內存做爲Java Heap。根據系統機制,若是分類的內存還沒有真正使用,就不計入PrivateDirty和PSS。例如圖1-8,Heap Size/Alloc不少,但大部分是共享,實際使用的較少。因此反映到PrivateDirty/PSS裏的內存並很少。工具

圖1-8 共享內存較多的進程性能

2) New對象以後,因爲要向對應的地址寫入數據,內核開始真正分配該地址對應的4K物理內存頁面。

Alloc.cpp, 176行起:

圖1-9 DVM虛擬機分配內存的代碼

3) 運行一段時間後,開始GC,有些對象被回收了,有些會一直存在,如圖1-10所示。

圖1-10黑點表示的內存會被回收

4) 在GC時,有可能會進行trim。即將空閒的物理頁面釋放回系統,表現爲PrivateDirty/PSS降低。

HeapSource.cpp, 431行:

圖1-11 釋放內存回系統的代碼(一)

HeapSource.cpp,1304行:

圖1-12 釋放內存回系統的代碼(二)

1.3.2 問題所在

在瞭解DVM分配釋放內存的機制後,根據dumpsys觀察到的現象,猜想可能出現了頁利用率問題(頁內碎片)。如圖1-13所示,第一行:在開始階段,內存分配的較滿。第二行:通過GC後,大部分對象被釋放,少部分留下來。

圖1-13產生內存碎片

這種狀況下可能會產生的問題是,整頁的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所示。

圖1-14對頁面利用率進行分類統計

這張圖就是被測應用的頁面利用率分佈圖,左邊是利用率低的頁面,右邊是利用率高的頁面。若是發現利用率低的頁面數目增長,說明小對象碎片的數量增長了。

1.3.3 優化Dalvik內存碎片

爲了可以找出有問題的代碼,咱們將上一步獲得的數據繼續處理。取出全部使用不滿2K的頁面的內存塊地址,再使用OQL將地址導入到MAT中,分析地址對應的對象是什麼。如圖1-15所示就是將地址從新導入到MAT中獲得的對象列表了:

圖1-15內存碎片頁中的對象

在這裏基本就能看出來是哪些對象形成了內存的碎片化,數量比較多的前幾個類的天然嫌疑比較大,能夠先對前幾個類的相關代碼進行分析。也能夠對這些代碼進行鍼對性的內存測試,觀察內存狀況。

經過對生成這些對象的代碼分析和模擬實驗,咱們還原出問題的基本過程:

  • 生成對象過程須要較多的臨時變量。
  • 批量生成過程當中,因爲還有空閒內存,虛擬機沒有作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];
  }
}

圖1-16顯示了相似狀況下數組的分配範圍,可見數組中每一個成員的內存地址都是不連續的,而且相隔很遠。這種狀況下就會消耗不少個物理內存頁面,增長Heap Free,形成例子中的問題。

圖1-16內存碎片對象地址的例子

經驗小結

根據上述的流程,咱們搞清楚了形成問題的緣由,而且找到了問題代碼。那麼應當總結一些經驗,以供借鑑。對於測試人員來講,有如下兩個經驗:

  • MAT是探索Java堆並發現問題的好幫手,可以迅速發現常見的圖片和大數組等問題。但僅靠MAT提供的功能也不是萬能的,好比這個問題的數據就隱藏在對象的地址中。
  • 對Android測試經驗來講,可能容易找到的是應用代碼及框架的各類測試經驗和指導,底層以及涉及性能的測試經驗並不太多。這方面能夠借鑑Linux系統的測試經驗,瞭解內核及進程相關的知識,熟悉經常使用工具。
  • 內存分配的最小單位是頁面,一般爲4K。

對於開發人員,如下兩個經驗也許能有幫助:

  • 儘可能不要在循環中建立不少臨時變量。
  • 能夠將大型的循環拆散,分段或者按需執行。

更多精彩內容歡迎關注騰訊優測的微信公衆帳號:

騰訊優測是專業的移動雲測試平臺,爲應用、遊戲、H5混合應用的研發團隊提供產品質量檢測與問題解決服務。不只在線上平臺提供app自動化測試、雲真機遠程操控與調試、私有自動化測試工具XTest等多種質量檢測工具,更爲VIP客戶配備了專家團隊提供定製化綜合測試解決方案。

相關文章
相關標籤/搜索