Android記憶體分析和調優(上)
第一層 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,下次寫如何具體分析這五項。
相關文章
- Android記憶體分析和調優(中)Android記憶體
- Android記憶體分析和調優(下)Android記憶體
- JVM原理講解和調優,記憶體管理和垃圾回收,記憶體調優JVM記憶體
- JVM效能調優,記憶體分析工具JVM記憶體
- 效能調優(cpu/IO/JVM記憶體分析)JVM記憶體
- oracle 記憶體分配和調優 總結Oracle記憶體
- Android記憶體優化(五)詳解記憶體分析工具MATAndroid記憶體優化
- Android記憶體優化——記憶體洩露檢測分析方法Android優化記憶體洩露
- MongoDB記憶體使用分析和優化MongoDB記憶體優化
- Android 效能優化之記憶體洩漏檢測以及記憶體優化(上)Android優化記憶體
- ANDROID記憶體優化(大彙總——上)Android記憶體優化
- CDH叢集調優:記憶體、Vcores和DRF記憶體
- 【Spark篇】---Spark調優之程式碼調優,資料本地化調優,記憶體調優,SparkShuffle調優,Executor的堆外記憶體調優Spark記憶體
- 記憶體洩露例項分析 -- Android記憶體優化第四彈記憶體洩露Android優化
- SAP ECC6.0記憶體引數調整和調優記憶體
- Oracle記憶體引數調優Oracle記憶體
- 關於redis記憶體分析,記憶體優化Redis記憶體優化
- Android記憶體優化Android記憶體優化
- Android 記憶體優化Android記憶體優化
- Android 記憶體優化(二)DVM 和 ART 的 GC 日誌分析Android記憶體優化GC
- Android記憶體優化之記憶體快取Android記憶體優化快取
- Java虛擬機器學習 - 記憶體調優Java虛擬機機器學習記憶體
- 分析並優化 Android 應用記憶體佔用優化Android記憶體
- Android記憶體優化(一):Java記憶體區域Android記憶體優化Java
- Android效能優化篇之記憶體優化--記憶體洩漏Android優化記憶體
- Android Note - 記憶體優化Android記憶體優化
- android 記憶體優化篇Android記憶體優化
- Android效能優化 - 記憶體優化Android優化記憶體
- SAP專家培訓之NetweaverABAP記憶體管理和記憶體調優最佳實踐記憶體
- Android 記憶體洩漏分析Android記憶體
- Android記憶體溢位分析Android記憶體溢位
- 【Spark篇】---Spark中記憶體管理和Shuffle引數調優Spark記憶體
- AIX記憶體效能調優(svmon sar vmo)AI記憶體
- SAP專家培訓之Netweaver ABAP記憶體管理和記憶體調優最佳實踐記憶體
- Android記憶體優化(一)DVM和ART原理初探Android記憶體優化
- Android 效能優化之記憶體優化Android優化記憶體
- 淺談Android記憶體優化Android記憶體優化
- Android記憶體優化全解析Android記憶體優化