dumpsys meminfo 的原理和應(yīng)用

https://mp.weixin.qq.com/s/pwOFI-l2seDrZgik-KvIMg

Android中通過命令dumpsys meminfo package_name|pid,?查看指定進(jìn)程的內(nèi)存使用情況.通過輸出的信息,可以看出來應(yīng)用在內(nèi)存哪里分配出現(xiàn)了問題,比如native heap?分配高,就要查一下自己的native部分的代碼哪里分配后沒有及時(shí)釋放。

這條命令是如何工作的?

Android中的Service都會注冊到ServiceManager,每個(gè)Service要實(shí)現(xiàn)binder定義的接口,其中的dump方法dump(int fd, const Vector<String16>& args),通過給定的參數(shù),把信息送到文件描述符fd中。

dumpsys位于/system/bin/dumpsys.運(yùn)行時(shí)它首先后獲得ServiceManager,然后根據(jù)參數(shù)查找服務(wù),找到后,然后binder調(diào)用服務(wù)的dump方法.而meminfo是系統(tǒng)的一個(gè)服務(wù)(內(nèi)部則是一個(gè)MemBinder的類實(shí)現(xiàn)的),當(dāng)dumpsys找到這個(gè)服務(wù)后,調(diào)用dump方法,通過傳遞的參數(shù),顯示出進(jìn)程內(nèi)存使用情況。

?下面是一個(gè)進(jìn)程的meminfo。


上面圖中的數(shù)據(jù)分A和B兩個(gè)部分, A 部分是進(jìn)程內(nèi)存的詳細(xì)信息, B 部分是對詳細(xì)信息進(jìn)行的總結(jié)。

而A部分的數(shù)據(jù)是從哪里讀到的?

通過以下三個(gè)方式,圖中A.1部分的信息是通過解析/proc/pid/smaps獲得的, A.2 通過IPC (binder 和pipe)方式獲得。而A.3通過memtrack.vendor_xxx.so中的接口獲得的。

下面將從A, B 兩個(gè)部分,詳細(xì)介紹這些數(shù)值是如何生成的。

A部分

A.1 smaps

下面是meminfo讀取進(jìn)程的smaps從上到下的調(diào)用關(guān)系圖。


讀取進(jìn)程smaps的節(jié)點(diǎn)

根據(jù)屬性的不同,進(jìn)程的虛擬地址空間被劃分成若干個(gè)vma,每一個(gè)vma通過vm_next和vm_prev組成雙向鏈表,鏈表頭位于進(jìn)程的task->mm->mmap.當(dāng)通過proc接口讀取進(jìn)程的smaps文件時(shí),內(nèi)核會首先找到該進(jìn)程的vma鏈表頭,遍歷鏈表中的每一個(gè)vma, 通過walk_page_vma統(tǒng)計(jì)這塊vma的使用情況,最后顯示出來。

下圖是一塊vma的統(tǒng)計(jì)信息


解析smaps用到了以下幾個(gè)字段.

Pss

PSS (Proportional Set Size) = 進(jìn)程獨(dú)占的內(nèi)存 + 進(jìn)程程共享的內(nèi)存 / 映射次數(shù)。

內(nèi)存的管理是以 page 為單位的, 如果 page 的 _refcount或者 _mapcount為 1, 那么就是進(jìn)程獨(dú)占的內(nèi)存. 也叫 private. 如果 page 的 _mapcount 為 n ( n >= 2), 這塊內(nèi)存以 page / n 來統(tǒng)計(jì)。

Private_Dirty

Private 在上面已經(jīng)說過了。 而 Dirty 分為 PageDirty和 pte_dirty. PageDirty就是所說的臟頁( 文件讀到內(nèi)存中被修改過, 就會標(biāo)記為臟頁)。 pte_dirty則當(dāng) vma 用于 anonymous 的時(shí)候, 讀寫這段 vma 時(shí)候, 觸發(fā) page fault, 調(diào)用 do_anonymous_page , 如果vma_flags中包含 VM_WRITE, 則會通過 pte_mkdirty(entry)標(biāo)記。

Private_Clean

與 Private_Dirty 相反。

Swap

一般情況下, 在 Android 中就是 zram, 通過壓縮內(nèi)存頁面并將其放入動態(tài)分配的內(nèi)存交換區(qū)來增加系統(tǒng)中的可用內(nèi)存量, 壓縮的都是匿名頁。

但下面這段vma是文件映射的,但還有swap字段的,這是因?yàn)檫@個(gè)文件是通過mmap到進(jìn)程地址空間的。當(dāng)標(biāo)記中有MAP_PRIVATE時(shí),這表示是一個(gè)copy-on-write的映射,雖然是file-backed , 但當(dāng)向這個(gè)數(shù)據(jù)寫入數(shù)據(jù)的時(shí)候,會把數(shù)據(jù)拷貝到匿名頁里,所以看到上面的Anonymous:也不為0。


A.1信息的分類則通過vma的名字進(jìn)行字符串匹配,然后將內(nèi)存信息劃分為不同的部分

1. vma用于file時(shí)

當(dāng)vma->vm_file不為空的時(shí)候,名字為file->f_path文件路徑,文件包含存儲介質(zhì)的文件,也包含設(shè)備文件,虛擬文件等(everything is a file)。

下面是meminfo的字段對照表


例如在解析vma過程中,如果vma的名字包含/dev/kgsl-3d0的時(shí),就把它歸在Gfx Dev 這類。

2. vma 用于anonymous時(shí)

這種用途的vma的名字[anon:開頭,雖然叫匿名,但顯示哪里分配的,比如說上圖的[anon:libc_malloc],就說明這段vma是從libc_malloc中分配出去的。

老的內(nèi)核版本是沒有這個(gè)域的,后來因?yàn)閡serspace的進(jìn)程有很多種不同的分配器(allocators) , 如果出現(xiàn)異常不知道哪里出了問題,就比如說如何區(qū)分Dalvik Heap 和Native Heap, 后來android提了一筆可以修改內(nèi)存區(qū)域的名字的補(bǔ)丁,通過prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start, len, (unsigned long)name)系統(tǒng)調(diào)用,就可以把名字保存在vma_area_struct ->shared.anon_name有趣的這是個(gè)userspace指針,來降低mm子系統(tǒng)的復(fù)雜性.同時(shí)復(fù)用了vma_area_struct結(jié)構(gòu)體的shared(利用union),shared用在file-backed mappings,這樣就不和用于匿名的vma沖突.連內(nèi)存都省了(這可能就是程序的魅力所在)。


A.2進(jìn)程中讀取

下面是從meminfo讀取進(jìn)程運(yùn)行狀態(tài)的信息調(diào)用圖.這部分信息是通過IPC方式獲得。


Meminfo打開一個(gè)pipe,通過異步binder調(diào)用并傳遞了pipe fd,?然后開始監(jiān)聽fd,應(yīng)用進(jìn)程獲得了這個(gè)pipe fd后 讀取需要的內(nèi)存數(shù)據(jù),通過pipe傳遞到AMS中。

mallinfo()

返回內(nèi)存分配的統(tǒng)計(jì)信息, 函數(shù)聲明在 android/bionic/libc/include/malloc.h,函數(shù)定義在 android/bionic/libc/bionic/malloc_common.h.



函數(shù)調(diào)用Malloc(mallinfo),這是對mallinfo進(jìn)行封裝,來加載不同的分配器對應(yīng)函數(shù)的實(shí)現(xiàn).因?yàn)锳ndroid使用的是jemalloc,也就會調(diào)用je_mallinfo()。

malloc結(jié)構(gòu)體定義,返回struct mallinfo。

下面是meminfo和mallinfo字段對照表


通過上面獲得Java虛擬機(jī)的內(nèi)存使用情況

下面是meminfo和runtime的字段對照表


A.3 memtrack.vendor_xxx.so

下面是meminfo讀取進(jìn)程在GPU上分配內(nèi)存的從上到下調(diào)用關(guān)系圖。

讀取進(jìn)程在GPU上分配的內(nèi)存

每個(gè)廠商統(tǒng)計(jì)的策略可能不一樣,這也是出現(xiàn)HAL層的原因. 下面以高通SDM710為例。從代碼中可以看到也是讀內(nèi)核中GPU文件節(jié)點(diǎn),然后解析數(shù)據(jù)的。


B部分?

Java Heap


dalvik private dirty包含任何寫過zygote分配的頁面(應(yīng)用是從zygote fork 出來的),和應(yīng)用本身分配的。

art mmap是應(yīng)用的bootimage,任何private頁面也算在應(yīng)用上。

Native Heap


nativePrivateDirty;?// libc_malloc

通過libc_malloc庫分配的大小

Code


所有私有靜態(tài)資源求和。


Stack

// stack private_dirty

getOtherPrivateDirty(OTHER_STACK);

進(jìn)程本身?xiàng)U加玫拇笮 ?/p>

Graphic

//Gfx Dev private_dirty + private_cleangetOtherPrivate(OTHER_GL_DEV)

// EGL mtrack private_dirty + private_clean

+?getOtherPrivate(OTHER_GRAPHICS)

// GL mtrack private_dirty + private_clean

+?getOtherPrivate(OTHER_GL);

進(jìn)程在GPU上分配的內(nèi)存。

System

Pss Total 的和- Private Dirty 和Private Clean 的和

系統(tǒng)占用的內(nèi)存,例如一些共享的字體,圖像資源等。

應(yīng)用

當(dāng)我們查看應(yīng)用的內(nèi)存消耗情況時(shí),可以通過meminfo提供的信息,判斷應(yīng)用中哪個(gè)模塊使用的內(nèi)存出現(xiàn)問題。

下面是同一個(gè)應(yīng)用A和B版本的meminfo:


A版本

B版本

對比A B 版本,先看App Summary, 可以明顯發(fā)現(xiàn)Graphic占用高,而Graphic是由Gfx, EGL 和GL組成,從兩圖對比看主要是EGL部分, EGL 是進(jìn)程在GPU上分配的內(nèi)存,查看平臺對應(yīng)的GPU內(nèi)存調(diào)試的接口



通過對比發(fā)現(xiàn)B版本EGL部分主要是發(fā)生在使用egl_image上,查看代碼發(fā)現(xiàn)egl_image是從hardware bitmap 分配的,這是Android O 上的一個(gè)bitmap的新特性。B 版本使用后,沒有及時(shí)釋放所致。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容