天天看點

android記憶體分析

在任何軟體開發環境中,ram都是非常寶貴資源。在移動作業系統裡,由于實體記憶體的限制,它會變得更加的寶貴。雖然android的dalvik虛拟機會正常的執行垃圾回收,但是開發人員仍然不能忽略什麼時候、在哪裡申請和釋放記憶體資源。

為了能夠使垃圾回收器從應用裡正常的回收記憶體資源,開發人員需要避免産生記憶體洩露,注意在合适的時候釋放引用reference(記憶體洩露常常由于保持着全局變量的引用)。對于大多數應用,dalvik垃圾收集器會處理大部分的回收工作:系統會在對應脫離活動線程的作用域後回收你申請的記憶體資源。

<b>一、 </b><b>如何進行記憶體監測分析</b>

<b>1. </b><b>通過垃圾搜集日志進行分析</b>

分析應用記憶體最簡單的方式是檢測dalvik的日志,每一次垃圾收集,logcat會列印下面格式的日志:

<b>d/dalvikvm: &lt;gc_reason&gt; &lt;amount_freed&gt;, &lt;heap_stats&gt;, &lt;external_memory_stats&gt;, &lt;pause_time&gt;</b>

如:

<b>d/dalvikvm( 9050): gc_concurrent freed 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms</b>

l gc reason:是什麼觸發了垃圾收集、是那種類型的垃圾收集。如gc_concurrent代表記憶體快填滿時申請記憶體而觸發的一次并發垃圾收集。gc_for_malloc代表記憶體占用已滿時申請記憶體而觸發的一次中斷性垃圾收集,系統需要停止你的應用的執行來進行記憶體回收。gc_hprof_dump_heap代表你分析記憶體時建立了一個記憶體hprof檔案導緻垃圾收集。gc_explicit:一次明确執行的垃圾收集,如你調用了gc()方法(請盡量不要這麼作)gc_external_alloc:隻發生在api level 10或者更低的版本上,應用外配置設定記憶體的垃圾收集,如本地記憶體裡存儲的像素資料、nic的位元組緩存。

l amount feed:垃圾收集回收的記憶體大小

l heap stats:可用記憶體比例

l external memory stats:外部申請的記憶體量/記憶體回收觸發門檻值,僅在api level 10或者低版本有

l pause time:中斷時間。并發垃圾收集時會中斷兩次,分别在垃圾收集的開始和快結束時。

重點關注65% free 3571k/9991k的比例變化,如果持續增長而未能回收代表存在記憶體洩露。

<b>2. </b><b>可視化觀察記憶體變化</b>

為了得到你的應用正在使用什麼類型的記憶體什麼時候使用的資訊,可以通過裝置監控程式來實時的浏覽你應用的記憶體變化,操作步驟:打開裝置監控(&lt;sdk&gt;/toos/monitor) =》 左邊選擇應用程序 =》 左上角點選”update heap” =》 右側點選”heap”。

heap視圖可以展示你應用記憶體使用的統計資訊,并在每次垃圾搜集後進行更新。通過觀察可以得到記憶體配置設定回收的實時資訊進而幫助你決策合适申請和釋放記憶體資源。

<b>3. </b><b>跟蹤應用記憶體配置設定</b>

當你縮小了記憶體問題的範圍時,你可以通過allocation tracker來更好的了解記憶體的配置設定及分布,它不僅可以觀察特殊記憶體的使用,還可以用來分析應用配置設定記憶體的關鍵代碼路徑。

比如,在你的應用裡拖動list時跟蹤記憶體配置設定情況可以使你觀察的這個動作發生時的全部記憶體配置設定情況、什麼樣的線程在運作、記憶體配置設定是從哪裡觸發的。這對于你需要通過減少代碼路徑來減少工作提升界面平滑性的場景十分有價值。程式啟動路徑如下:打開裝置監控(&lt;sdk&gt;/toos/monitor) =》 左邊選擇應用程序 =》右側點選”allocation tracker” =》 點選”start tracking” =》 想要更新時點選”get allocations”。

清單中顯示了最近配置設定的全部記憶體,目前限制在512個執行個體緩存。點選配置設定的記憶體對象資訊可以看到配置設定記憶體的代碼執行堆棧路徑。這不僅跟蹤展示了配置設定了什麼類型的對象記憶體,還展示了線程資訊、類資訊、哪個檔案哪行代碼。

<b>4. </b><b>觀察應用整體記憶體配置設定情況</b>

為了更深入的分析,你可能需要觀測你的應用使用的不同類型的記憶體的分布情況,此時可以通過指令:adb shell dumpsys meminfo &lt;package name&gt;,輸出會已k為機關展示應用目前配置設定的全部記憶體,通常包括如下兩類:

l 私有記憶體private(clean和dirty的):你的應用程序單獨使用的記憶體,代表着系統殺死你的程序後可以實際回收的記憶體總量。通常需要特别關注其中更為昂貴的dirty部分,它不僅隻被你的程序使用而且會持續占用記憶體而不能被從記憶體中置換出存儲。你申請的全部dalvik和本地heap記憶體都是dirty的,和zygote共享的dalvik和本地heap記憶體也都是dirty的。

l proportional set大小(pss):這是加入與其他程序共享的分頁記憶體後你的應用占用的記憶體量,你的程序單獨使用的全部記憶體也會加入這個值裡,多程序共享的記憶體按照共享比例添加到pss值中。如一個記憶體分頁被兩個程序共享,每個程序的pss值會包括此記憶體分頁大小的一半在内。

pss的特性使得可以通過累加全部程序的pss值來衡量多個程序真正使用的記憶體量。這是與其他程序比較記憶體實際使用量的真實名額。指令輸入的字段的解釋如下:

l dalvik heap:dalvik虛拟機使用的記憶體。pss值包括了zygote申請的記憶體在内(會按共享的程序數按比例計算),private dirty是你的應用真實單獨使用的記憶體,包括從zygote程序fork出你的程序後你的應用和zygote曾經修改過的記憶體。

l davik other:如jit和垃圾收集之類的davik占用記憶體。如果沒有此選項行證明是老的版本,上面的記憶體占用會被累加到dalvik heap裡。

l heap alloc:累加了dalvik和native的heap,是以會比dalvik heap大。因為你的程序是從zygote中fork出來的,是以包括了與其他程序共享的記憶體。

l .so mmap及.dex mmap:加載本地so代碼合dalvik虛拟機dex代碼的映射占用的記憶體。同理pss total包括了與其他程序共享的代碼的記憶體占用。private clean是自己應用代碼占用的記憶體。但是通常情況下,真實的記憶體映射尺寸要更大一些,這個值隻代表目前需要加載到記憶體裡的執行過的代碼占記憶體大小,然而.so被加載到最終的位址時需要固定連接配接到本地代碼會占用大量private dirty。

l unknown:無法歸類到其他類别裡記憶體占用,通常是一些無法被工具識别的本地記憶體配置設定,類似dalvik heap,也包括 pss total和private dirty。

l total:上面全部條目的累加值,全局的展示了你的程序占用的記憶體情況,可以用來與其他程序進行比較。clean記憶體是從持久化的檔案(如執行過的代碼)映射占用的記憶體,如果一段時間比使用會被置換掉。

l viewrootimpl:你的應用程序裡的活動視窗視圖個數,可以用來監測對話框或者其他視窗的記憶體洩露。

l appcontexts及activities:應用程序裡context和activity的對象個數,可以用來監測activity的記憶體洩露,通常是由于一個靜态的引用導緻,如view和drawable都可以持有起始的activity的引用。

<b>5. </b><b>進行記憶體dump</b><b>分析</b>

記憶體dump包含了應用記憶體裡的全部的對象資訊,通常存儲為二進制的hprof檔案,可以通過分析這個檔案找尋記憶體問題。ddms裡點選”dump hprof file”即可dump記憶體,然後點選save儲存。也可以通過在代碼裡調用dumphprofdata()來更精确的控制dump記憶體時機。與java的dump稍有不同,android的記憶體dump含有大量的zygote配置設定的記憶體對象,由于是多程序共享的,通常不需要特殊分析。

可以使用mat來分析dump檔案,但要首先轉換為j2se标準的格式。可以使用&lt;sdk&gt;/platform-tools/内的hprof-conv工具實作,簡單使用原始檔案和轉換後的檔案兩個參數執行即可:hpfor-conv heap-original.hprof heap-converted.hprof。注意如果使用的是eclipse插件版本的ddms時系統會自動幫你轉換。

通常,分析記憶體時注意檢查如下點:

l 對activity/context/view/drawable的長期存在的引用或者間接引用

l 非靜态的内部類,如runnable,會持有activity執行個體的引用

l 長期持有對象引用的緩存

mat另一個強大的功能是進行多個dump的對比,可以通過把每個dump打開後執行window&gt;navigation history&gt;右鍵histogram&gt;add to compare baset,最後執行compare the result來對比。

<b>6. </b><b>進行觸發記憶體洩露的測試</b>

使用上述工具前需要壓測你的應用直到記憶體洩露産生,通常需要運作一段時間後進行分析,大的記憶體洩露通常會在配置設定的最大記憶體部分看到,越小的記憶體洩露需要越長時間進行測試。你可以手動或者使用monkey嘗試如下兩種方法重制記憶體洩露:

l 在不同的activity内持續旋轉裝置,因為旋轉裝置會觸發activity重繪。

l 在不同的activity狀态裡在不同的應用之間跳轉。

<b>二、 </b><b>備注及資料</b>

<b>1. </b><b>paging</b>

分頁是作業系統管理記憶體的一種方案,通過從第二存儲區存取資料來使用主存。通過第二存儲區讀寫的資料會被分隔為相同大小的塊(起名叫分頁)。分頁的最大好處是使得程序使用的實體記憶體位址不再連續,否則系統要将整個應用程式或者程式的整個部分連續存儲到記憶體,而這非常容易産生多樣的存儲和碎片問題。

windows nt類系統使用pagefile.sys檔案來分頁,預設存儲在windows安裝的根目錄。

unix或者unix類系統使用swap來實作類似功能,swap既、用來在記憶體和硬碟之間移動資料又用來存儲在硬碟上的分頁資訊。通常會使用整個磁盤分區來作為swap,這些分區叫做swap分區。

linux内使用swap檔案同樣可以達到unix的swap分區的速度,但是swap檔案的限制是需要在檔案系統内來申請空間。為了提高性能,linux核心儲存了一份swap檔案在裝置上的位置目錄資訊的map緩存,進而避免了檔案系統的超負荷過載。red hat認為還是應該使用swap分區,因為可以将分區存儲到磁盤的高速讀取和搜尋的位置,進而充分利用磁盤提高性能。但是swap檔案的靈活性遠遠大于swap一個磁盤分區,可以被存儲在檔案系統任何位置,可以增加、修改。

<b>2. </b><b>swap</b>

linux的swap可以在實體記憶體不足時派上用場,此時可以将實體記憶體的一部分空間釋放出來供目前運作的程式使用。被釋放的空間通常是很長時間沒有什麼操作的程式,這些被釋放的空間會儲存到swap空間中,等到需要運作時再從swap中恢複。

<b>3. </b><b>memory-mapped file</b>

記憶體映射檔案是一塊虛拟的記憶體區域,被配置設定用來提供與檔案(或類似檔案類的資源)進行位元組到位元組的關聯。典型的映射資源是存儲在磁盤上的實體檔案,但也有可能是一個裝置、共享的記憶體對象、或者其他通過file descriptor可以描述的系統資源。一旦有了這種檔案和記憶體的映射關聯,應用程式可以像通路記憶體一樣通路這塊映射區域。

記憶體映射檔案的最大好處是提供了i/o性能,尤其是使用大檔案時。不過小的檔案會造成空間浪費,因為記憶體映射大小會被設定為分布到多個分頁上(每個分頁大都是4k),這樣5k的檔案需要兩個分頁占用了8k空間。記憶體映射檔案同時提供了懶加載的能力,使得一個巨大的檔案可以使用很少的記憶體。

記憶體映射檔案使用最廣泛的地方是作業系統(windows和unix),系統程序啟動後,系統會使用記憶體映射檔案來将可執行檔案和其子產品讀入記憶體來執行。記憶體映射系統通常會使用demand paging技術,隻會加載真正需要執行的檔案。

記憶體映射檔案另一個廣發應用是在多程序中共享記憶體。在當今受保護模式類的作業系統裡,一個程序是不允許通路另一個程序使用的記憶體區域的,記憶體映射檔案i/o是最常用的解決這個問題的方式,使得多個程序可以映射同一個實體檔案然後通過記憶體通路。

<b>4. </b><b>資料來源</b>

android-sdk/docs/tools/debugging/debugging-memory.html

<a href="https://en.wikipedia.org/wiki/paging">https://en.wikipedia.org/wiki/paging</a>

https://en.wikipedia.org/wiki/memory-mapped_file

繼續閱讀