Android內存分析和調優(上)

原文:http://www.cnblogs.com/zdwillie/p/3259395.html?utm_source=tuicool


最近我們的android app佔用了大量內存,於是領導安排做減少內存佔用的工作。
要優化內存,首先要做的就是分析內存佔用情況。android提供了多個工具和命令進行內存分析。
 

第一層 Procrank

 
很粗略的,可以使用"adb shell procrank",結果類似於

PID    Vss        Rss        Pss       Uss      cmdline

......
2319 42068K 42032K 13536K 7028K com.xxx
......

該命令可以列出當前系統所有進程的內存佔用情況。
PID是進程ID。
Vss是佔用的虛擬內存,如果沒有映射實際的內存也算進來。
Rss是佔用的物理內存。是共享內存+私有內存。因爲共享內存是多個進程共用的,所以存在重複計算。
Pss是佔用的私有內存加上平分的共享內存。例如一塊1M的共享內存被兩個進程共享,那每個進程分500K。各進程的Pss相加基本等於實際被使用的物理內存,所以這個經常是最重要的參數。
Uss是私有內存。
cmdline可以看做是apk包名。

通過procrank,只能很宏觀的橫向比較不同的應用。如果要更細緻的瞭解具體內存是如何使用,則需要進入

第二層 dumpsys meminfo

命令“adb shell dumpsys meminfo package.name”。在4.0 ICS(或者3.0 HoneyComb)之後的系統上,會看到類似下面的輸出

                                 Shared   Private  Heap    Heap     Heap
                      Pss      Dirty      Dirty     Size     Alloc      Free
                      ------   ------    ------     ------  ------     ------
Native            16        8           16        3416   3300     79
Dalvik            3884    10592   3580    9560   9022     538
Cursor            0          0           0 
Ashmem        0           0           0 
Other dev       5110    10244   0 
.so mmap       640     1948      396 
.jar mmap      0          0           0 
.apk mmap     68        0           0 
.ttf mmap       817      0           0 
.dex mmap     411      0           0 
Other mmap   55        16         32 
Unknown        2404     660       2388 
TOTAL            13405  23468   6412    12976 12322 617

(如果使用2.3或之前的版本,結果會粗糙一些,很多都被歸入了Other,但基本結構是一樣的)

stacktrace上有個經常被搜到的帖子對這個格式有說明,雖然針對的是android 2.3格式,但讀後非常有收穫。
但仍有很多疑問沒有解答,例如針對上面的例子,爲什麼Native heap size那麼大,但Pss卻那麼小?佔用內存比較多的Other dev是什麼?Unknown又有哪些?等等。
要理解這些,需要知道這個report是如何生成的。實際上,生成report的代碼是android的android_os_Debug.cpp
從中我們可以發現,上面列表的數據是由三種方式獲取的:
1. Pss/Shared Dirty/Private Dirty三列是讀取了/proc/process-id/smaps文件獲取的。它會對每個虛擬內存塊進行解析,然後生成數據。
2. Native Heap Size/Alloc/Free三列是使用C函數mallinfo得到的。
3. Dalvik Heap Size/Alloc/Free並非該cpp文件產生,而是android的Debug類生成。

後面兩個Heap的獲取比較簡單,我唯一的疑惑是爲什麼有free的?我的理解是無論是c的malloc還是java的new,最後都是通過mmap系統調用進行內存分配的。而mmap必須以頁的4K爲單位。所以如果一次一次只需要malloc 2K,則剩下的2K是free的。如果下次再malloc 2K,可以仍然使用上次mmap剩餘的2K內存。

至於smaps文件,我們可以通過adb shell cat /proc/process-id/smaps來查看(需要root)。這是個普通的linux文件,描述了進程的虛擬內存區域(vm area)的具體信息。每次mmap一般都會生成一個vm area。
在Android上,一個更加方便的命令是adb shell showmap -a process-id。

第三層 adb shell showmap

該命令也是讀取smaps文件,但結果細化的具體的vm area。
該命令輸出的每行表示一個vm area,列出了該vm area的start addr, end addr, Vss, Rss, Pss, shared clean, shared dirty, private clean, private dirty,object。 
第二層的dumpsys meminfo其實就是讀取這些數據,然後分類(native, dalvik, .so map, etc.)統計生成。
start addr和end addr表示進程空間的起止虛擬地址。
Vss,Rss,Pss跟前面說的一樣。
Object可以看做mmap的文件名。

Shared clean,按字面意思,表示共享的乾淨的數據。共享表示多個進程的虛擬地址可以都指向這塊物理空間,表示多個進程共享的so庫。爲什麼這裏說是多個進程共享的so而不是所有的so呢?
關於so庫的加載,我一直覺得是mmap帶MAP_SHARED參數,但看了memory_faq,才知道是MAP_PRIVATE。如果使用showmap命令查看vm area,會發現有的so的內存都屬於Shared clean,而有的so則屬於private clean。前者一般是當前進程特有的so,而後者一般是通用的so。後來看了對mmap的各種參數的實驗(很贊實踐精神),才知道第一次以MAP_PRIVATE mmap so,內存都是private clean的。如果另外一個進程mmap了同一個so,那該vm area就變成shared clean了。

Private clean,包括該進程私有的乾淨的內存。包括前面說的該進程獨自使用的so和進程的二進制代碼段。
Clean內存的好處是在內存緊張時,可以釋放物理內存。因爲是clean的,所以不需要寫回到disk,只需要下次讀取該內存(導致缺頁錯誤)時再從disk讀入。

Private dirty,表示該進程私有的不跟disk數據一致的內存段。例如堆(heap),棧(stack),bss段。關於bss段,因爲在elf文件爲了節約控件沒有賦值,所以在加載到內存時賦值爲0,於是跟disk就不一致了。在showmap結果中,會發現幾乎每個so都有一個顯示位[bss]的private dirty段。數據段我估計是private clean的,因爲elf文件是有初值的。

Shared dirty開始我一直搞不清楚。後來看了Dalvik vm internal這個video(slides),才明白了些。對於普通的linux進程,當父進程fork子進程時,父進程的虛擬內存區域都會”複製“一份到子進程中。這裏”複製“加引號,是因爲爲了節省內存,也爲了減少內存拷貝的時間,使用的是copy-on-write的方法。當子進程對private dirty的堆,棧,bss沒有修改時,則是父子進程share這份dirty(因爲跟disk沒法映射)數據。如果發生改變,則會修改爲private dirty。所以android有zygote進程,是所有android apps進程的父進程,在其中會加載resource等資源(下文會看到,最簡單的應該也有大概5M resource,例如圖片),這些資源都是隻讀的。具體的apps繼承了這些shared dirty的數據,因爲不修改它們,所以也不用分配多餘的內存空間。

由於android使用的linux沒有swap分區,所以dirty的數據必須常駐內存。所以dumpsys meminfo會把private dirty和shared dirty重點列出來,這也是我們優化內存的重點。

現在可以回答一個前面提到的問題,爲什麼Native Heap(根據mallinfo系統調用得到)很大而Native Pss(根據swaps得到)很小。我覺得這是dumpsys meminfo的一個bug。根據android_os_Debug.cpp的代碼,object名字是[heap]的段被認爲是native heap。這在2.3是正確的,但在4.0之後,[heap]爲名字的段卻很小(只有幾K)。同時,我卻發現有大量的[anon]的區域。我認爲anon是anonymous的縮寫。malloc一般是通過mmap來分配內存的,而參數是MAP_ANONYMOUS。所以我覺得這些[anon]是native heap。從大小上看,現在這些[anon]被看做是Unkown的一部分,也跟hative heap的大小差不多。

在dumpsys meminfo結果的其他值比較大的行,.so表示映射的so庫(vm area行的object名稱包含.so字樣),.dex表示映射的.dex文件(dalvik的虛擬機二進制碼),Other dev表示映射其他的/dev的(dalvik的heap也是映射到特殊的/dev上)。加上native和dalvik的heap,下次寫如何具體分析這五項。


發佈了53 篇原創文章 · 獲贊 9 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章