windows2008开机占用多少内存_Android内存占用分析
1、為什么/proc/meminfo中的內存總大小比物理內存小?
2、怎么看Android還剩多少可用內存比較準確?
3、怎么看Kernel的內存占用比較準確?
4、是哪些因素影響了Lost RAM的大小?
5、怎么看一個進程的內存占用比較合適?
本文以Android P為例,對應kernel版本為4.14
1、 MemTotal
MemTotal 即 /proc/meminfo 中的第一行的值, 可以認為是系統可供分配的內存總大小, 通常大小會比實際物理內存小, 這個是為什么呢? 少的部分被誰占用了呢?
1.1 memblock
首先需要了解一下memblock.
在伙伴系統(buddy system)初始化完成前,Linux使用memblock來管理內存,memblock管理的內存分為兩部分: memory類型和reserved類型。 對應的描述變量分別是memblock.memory 和 memblock.reserved。
memblock中兩種類型的內存申請/添加函數如下:
//memory 類型的memblock申請/添加 int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size) int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size, int nid)//reserved 類型的mblock申請/添加 int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)1.1.1 物理內存分布
memblock是如何知道物理內存的分布的呢?
kernel啟動的過程中,從lk/uboot知道了DTB的加載地址, 在如下的調用流程中解析DTB下的memroy節點,將節點下的物理內存區間使用memblock_add()添加給memblock維護。 由于lk/uboot中可能使用內存,lk/uboot也可以修改DTB,所以這里memory節點下可能有多個物理內存區間。 多個物理區間中不連續的部分就是已經被lk/uboot占用的部分。
setup_machine_fdt(__fdt_pointer) //__fdt_pointer是傳遞的DTB加載地址|-->early_init_dt_scan()|-->early_init_dt_scan_memory() //解析memory node,遍歷其下的所有單元|-->early_init_dt_add_memory_arch()|-->memblock_add(base, size) //每個單元的起始地址和大小添加到memblock的memory類型中 //dts中的初始描述,0x40000000是起始物理地址//lk/uboot可以修改DTB中的節點,這里的reg單元可能有多個memory {device_type = "memory";reg = <0 0x40000000 0 0x20000000>;};可以通過 /sys/kernel/debug/memblock/ 下的節點查看兩種內存的物理空間分布:
lsg@eebbk:~$adb shell cat /sys/kernel/debug/memblock/memory lsg@eebbk:~$adb shell cat /sys/kernel/debug/memblock/reservedmemblock構建了內存的物理空間分布, 之后在伙伴系統(buddy system)初始化的過程中,構建了物理內存分布到虛擬內存空間的映射. memblock管理的memory類型的頁框都添加到各ZONE中, totalram_pages統計了它的頁框數目. 詳細代碼見 free_all_bootmem()
1.2 隱藏的內存占用
這里說幾個概念:
- 物理內存: 即DRAM物理內存大小,比如物理內存為2GRAM,則物理內存大小為2097152K
- memblock管理的內存: memblock管理的內存, 包含伙伴系統管理的內存和reserved兩部分,其頁框數為 get_num_physpages() 在下面的例子中, 可管理內存大小為 2045952K
- 伙伴系統管理的內存:伙伴系統管理的內存, 初始時對應memblock中的memory類型,其頁框數在kernel中對應全局變量 totalram_pages 在下面的例子中, 可分配內存大小為1983136K
關系:
物理內存 > memblock管理的內存 > 伙伴系統管理的內存
物理內存 = memblock管理的內存 + 預申請內存
memblock管理的內存 = 伙伴系統管理的內存 + reserverd內存(memblock的reserved type)
預申請內存:
預申請內存是指在memblock初始化前已經申請的內存,呈現給memblock的是這部分物理內存不存在,比如lk/uboot/bootloader用到的內存。 不同平臺這部分的占用大小不盡相同,有的可能為0.
Q1: memblock是怎么知道有些內存已經被占用的?A1: lk/uboot/bootloade修改DTB中memory節點下的空間區段(reg), kernel中讀取這些空間區段(reg),空間區段之間的內存空間就是被預申請已經占用了的.
//kernel log 中的輸出, 代碼實現在 mem_init_print_info() //關系:2097152K(物理2GB) = 2045952K(memblock管理的內存) + 51200K(預申請內存) //關系:2045952K(memblock管理的內存) = 1982176K + 63776K + 0K //1982176K 是此時 totalram_pages*4K 的大小, 也即memblcok管理的`memory type` 部分 //63776K 是memblcok管理的`reserved type` 部分 [ 0.000000] -(0)[0:swapper]Memory: 1982176K/2045952K available (12924K kernel code, 1384K rwdata, 4392K rodata, 960K init, 5936K bss, 63776K reserved, 0K cma-reserved)///proc/meminfo的輸出 //1983136 是此時 totalram_pages*4K 的大小 lsg@eebbk:~$adb shell cat /proc/meminfo MemTotal: 1983136 kBQ2: 為什么MemTotal的大小比實際物理內存小?A2: 這個部分比實際的物理內存小,就是少了上面說的兩個部分:預申請內存和kernel reserved內存. 預申請內存: 這部分的大小可以查看/d/memblock/memory相對物理內存空閑的部分. reserved內存: 這部分大小可以查看/d/memblock/reserved的大小,或者kernel log中的大小(上例中63776K)
Q3: 為什么開機時 totalram_pages的大小(上例中1982176K) 和 totalram_pages的大小(上例中1983136K) 開機后 不一致呢?A3: 這是因為memblock管理的reserved type中部分內存在初始化完畢后釋放了,添加到了伙伴系統(buddy system)中. 詳細代碼見free_initmem() 比如上面的log, 1982176K + 960K = 1983136K
<6>[ 2.258901] -(2)[1:swapper/0]Freeing unused kernel memory: 960KQ4: kernel代碼部分的占用在哪個部分體現?A4: 這部分包含 kernel code+rwdata+rodata+init+bss , 在 kernel log中已經輸出. 這部分的物理占用計算在reserved部分.詳細代碼見 arm64_memblock_init(). 另外kernel code物理區域也會vmap到虛擬地址空間,其vmap的區間可以看 /proc/vmallocinfo中帶有paging_init的行。
1.3 reserved包含哪些
前面說到,memblock管理的內存, 包含伙伴系統管理的內存和reserved兩部分。 那reserverd部分的內存占用又包含哪些呢?詳細分解來看,至少包含以下這些部分:
1、代碼 包含 kernel code+rwdata+rodata+init+bss 等,都計入到reserved部分。
分配路徑:
2、struct page 我們知道整個物理內存被分配為若干個頁框(page frame),一般大小位4K,一個頁框對應一個struct page結構。struct page的內存占用就是在reserved部分,物理內存越大,這個區域就越大。比如2GB RAM,可能需要32MB大小的struct page。
分配路徑:
3、percpu 為所有已定義的per-cpu變量分配副本空間,靜態定義的per-cpu變量越多,這個區域越大。
分配路徑:
4、devicetree 解析DTB消耗的內存
分配路徑:
5、dts中reserved節點 dts中通過reserved_memory節點申請的reserved內存
分配路徑:
2、 從Linux角度
/proc/meminfo 是從Linux的角度統計系統的內存占用情況.
2.1 /proc/meminfo
具體代碼在:
//fs/proc/meminfo.c static int meminfo_proc_show(struct seq_file m, void v)1、MemTotal
當前系統可使用的內存大小,對應全局變量MemTotal。 詳細見上面第一節的敘述。
2、MemFree
當前系統空閑的內存大小,對應所有處于NR_FREE_PAGES狀態的頁框。
3、MemAvailable
大致等于: MemFree + Active(file) + Inactive(file) + SReclaimable 此外還考慮了內存壓力水位(watermark)的情況,計算比較復雜,詳細見 si_mem_available(). 這只是理論上系統可用的內存,即理論上可回收的內存,但是實際上能用的達不到這么多。
4、Buffers
塊設備(block device)操作所占用的page cache大小。 塊設備的緩沖區大小,詳細見nr_blockdev_pages()
5、Cached
普通文件操作所占用的page cache大小。這里只是普通文件操作時的page cache,其實page cache還有swap cache 和 上面的Buffers。
計算方法:
6、Active/Inactive
Active = Active(anon) + Active(file)
Inactive = Inactive(anon) + Inactive(file)
內存中的頁分為匿名頁(anon)和文件頁(file)。
- 匿名頁(anon):特征是其內容與文件無關,比如malloc申請的內存,回收方法是交換到swap區。
- 文件頁(file):特征是其內容與文件相關,比如程序文件、數據文件所對應的內存頁,回收方法是回寫到磁盤或清空。
在內存回收中,采用的算法是LRU(Least Recently Used),LRU算法又將匿名頁(anon)和文件頁(file)都分為活躍(Active)和不活躍(Inactive)。內存回收時,首先回收的是不活躍頁(Inactive)。
7、Unevictable/Mlocked
Unevictable 對應LRU_UNEVICTABLE, 是LRU中不能被回收的頁。Mlocked 對應NR_MLOCK的頁。
8、SwapTotal/SwapFree
SwapTotal 對應 Swap 區的總大小。SwapFree 對應 Swap 區的剩余大小。
9、Dirty/Writeback
Dirty: 對應NR_FILE_DIRTY的頁,需要寫入磁盤的內存區大小 Writeback: 對應NR_WRITEBACK的頁,正在被寫回磁盤的大小
10、AnonPages/Mapped
AnonPages: 對應NR_ANON_MAPPED的頁,已映射的匿名頁(anon)大小 Mapped: 對應NR_FILE_MAPPED的頁,已映射的文件頁的大小,Mapped 是 Cached 的一部分
11、Shmem
對應NR_SHMEM的頁,是 tmpfs 和 devtmpfs 所使用的內存。
12、Slab/SReclaimable/SUnreclaim
Slab = SReclaimable + SUnreclaim 使用slab/slub/slob機制申請的內存大小,又分為可回收(NR_SLAB_RECLAIMABLE)和不可回收(NR_SLAB_UNRECLAIMABLE)兩部分。 可以通過 /proc/slabinfo 節點查看slab的內存信息。
13、KernelStack
對應 NR_KERNEL_STACK_KB的頁, 是所有task的內核棧的內存大小。
14、PageTables
對應 NR_PAGETABLE的頁,是頁表(page table)的占用大小,頁表(page table)的作用就是完成內存虛擬地址到物理地址的轉換。 還有一個相關概念是頁框(page frame),頁框(page frame)是內存管理的最小單位,就是物理頁。每一個物理頁都用一個對應的struct page結構體描述,struct page占用的內存在reserved內存中。
15、VmallocTotal
整個vmalloc的地址區間的大小,對應 (VMALLOC_END - VMALLOC_START),vmalloc的真實占用可以查看/proc/vmallocinfo。詳細見下一節。
2.2 /proc/vmallocinfo
vmalloc 用于在內核中分配虛擬地址空間連續的內存,/proc/vmallocinfo 展示了整個vmalloc區間(VMALLOC_END - VMALLOC_START)中已經分配的虛擬地址空間信息,每一行表示一段區間的信息。
單個區間的信息展示代碼在:
//mm/vmalloc.c static int s_show(struct seq_file *m, void *p) {struct vmap_area *va;struct vm_struct *v;va = list_entry(p, struct vmap_area, list);if (!(va->flags & VM_VM_AREA)) {seq_printf(m, "0x%pK-0x%pK %7ld %sn",(void *)va->va_start, (void *)va->va_end,va->va_end - va->va_start,//kernel 4.14上,這里考慮了VM_LAZY_FREE 的flag, 5.x版本又去掉了這一部分va->flags & VM_LAZY_FREE ? "unpurged vm_area" : "vm_map_ram");return 0;}v = va->vm;...... }/proc/vmallocinfo中每行輸出的最后一個字段表示這段區間的類型,kernel 4.14上顯示的類型有:
- ioremap(對應VM_IOREMAP)
- vmalloc(對應VM_ALLOC)
- vmap(對應VM_MAP)
- user(對應VM_USER)
- vm_map_ram(根據vmap_area.flag)
- unpurged vm_area(根據vmap_area.flag)
一段vmalloc區間是否已經在物理上分配對應的大小,要看具體的類型。
對于Android P來說,除ioremap,map_lowmem,vm_map_ram之外的類型都認為是有物理占用的,用VmallocUsed表示Android統計的Vmalloc內存占用。
但是如果按排除這幾個類型來統計,會有一定的偏差,在Android P(kernel 為4.14),存在的誤差主要表現在:
1、kernel code
kernel code的占用,包括kernel code+rwdata+rodata+init+bss,都計入了memblock reserved部分。
但是kernel code也會通過vmap映射到虛擬地址空間(見 setup_arch() --> paging_init() --> map_kernel()),這部分地址空間可以看含有paging_init關鍵字的行。
由于這部分默認的關鍵字為vmap, 所以是統計在 VmallocUsed中的。
2、binder
Binder的內存分配是用戶空間調用mmap(), 到kernel中binder_mmap()的流程中為用戶空間映射分配內存,但是只映射了VMA區間大小的( 比如BINDER_VM_SIZE)虛擬地址空間,并沒有分配物理頁,是等到binder傳輸需要使用內存時,才在binder_update_page_range()中申請物理頁。
比如 一個binder client 調用binder_mmap(),其VMA區間為1024K,但是可能目前實際只用到了4K。但1024K都被計入到VmallocUsed部分。
74bb4b1000-74bb5af000 r--p 00000000 00:13 8297 /dev/binder Size: 1016 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Rss: 4 kB Pss: 4 kB概括來說,binder占用的內存,在進程PSS中已經統計,又統計在了kernel中,而且統計的是整個VMA區間而非實際物理占用,這造成了統計上的誤差。
Android P上binder的內存信息歸為vmalloc,這就會統計到 VmallocUsed中,而在Android P之前版本上,內存信息類型為ioremap 。
3、unpurged vm_area
看s_show()中的代碼,在kernel 4.14上,對于va->flags含有VM_VM_AREA和VM_LAZY_FREE位的,類型標記為unpurged vm_area,而只有VM_VM_AREA的標記為vm_map_ram。
看內核的提交記錄,VM_LAZY_FREE是 在2017年7月為解決ioremap地址跳躍的問題所加上的,本意是對此類型做特殊標記。但不應該認為這種類型已經分配了物理內存,它仍然只是一段虛擬地址區間。
在2019年9月又去掉了這部分代碼,最新的5.3內核版本上已經沒有了VM_LAZY_FREE的標記。
綜上來看,unpurged vm_area標記的區間,Android不應該認為是物理內存占用,不應統計在 VmallocUsed中。
/proc/vmallocinfo的輸出示例:
//區間起始地址-區間結束地址 //kernel code的map 0x0000000000000000-0x0000000000000000 8912896 paging_init+0x104/0x6c0 phys=0x0000000080080000 vmap //binder 0x0000000000000000-0x0000000000000000 1044480 binder_alloc_mmap_handler+0x48/0x1cc vmalloc //unpurged vm_area 0x0000000000000000-0x0000000000000000 1044480 unpurged vm_area //進程棧,實際只分配了4頁,但地址空間有5頁 0x0000000000000000-0x0000000000000000 20480 _do_fork+0xdc/0x3ac pages=4 vmalloc2.3 free命令
free 命令的代碼見:
//external/toybox/toys/other/free.c void free_main(void) {struct sysinfo in; //系統接口,獲取內存信息,實質上與 /proc/meminfo 的內容相對應sysinfo(&in);xprintf("tttotal used free shared buffersn""Mem:%17s%12s%12s%12s%12sn-/+ buffers/cache:%15s%12sn""Swap:%16s%12s%12sn", convert(in.totalram),convert(in.totalram-in.freeram), convert(in.freeram), convert(in.sharedram),convert(in.bufferram), convert(in.totalram - in.freeram - in.bufferram),convert(in.freeram + in.bufferram), convert(in.totalswap),convert(in.totalswap - in.freeswap), convert(in.freeswap)); }其展示的內容示例如下:
H100:/ # free -ktotal used free shared buffers Mem: 1983136 1604832 378304 2240 71416 -/+ buffers/cache: 1533416 449720 Swap: 1048572 0 10485723、從Android角度
dumpsys meminfo 是從Android的角度統計系統的內存占用情況。
可以dumpsys meminfo -h查看支持的參數,這里說明不接參數的情況。
對應的代碼在:
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java private final void dumpApplicationMemoryUsage()展示的信息分為幾個部分: - 進程PSS情況 - Total PSS by process - Total PSS by OOM adjustment - Total PSS by category - 整體情況 - Total RAM - Free RAM - Used RAM - Lost RAM - ZRAM
3.1 整體情況
先看整體的內存占用情況,整體的內存占用情況在輸出結果的最后,示例如下:
Total RAM: 1,983,136K (status moderate)Free RAM: 1,016,150K ( 75,626K cached pss + 562,384K cached kernel + 378,140K free)Used RAM: 1,108,343K ( 758,523K used pss + 349,820K kernel)Lost RAM: -141,361KZRAM: 4K physical used for 0K in swap (1,048,572K total swap)Tuning: 128 (large 256), oom 322,560K, restore limit 107,520K (high-end-gfx)約定幾個變量的值: - totalPss: 是展示信息中Total PSS by process:下所有進程的內存占用之和. - cachedPss:是展示信息中 Total PSS by OOM adjustment:下的Cached部分進程的內存大小之和。
使用了如下的調用關系主要讀取 /proc/meminfo的信息,將對應字段放到 MemInfoReader.mInfos[]數組中,展示的信息就是MemInfoReader.mInfos[]中信息的組合。
MemInfoReader.readMemInfo()//frameworks/base/core/java/android/os/Debug.java--> Debug.getMemInfo()//frameworks/base/core/jni/android_os_Debug.cpp--> android_os_Debug_getMemInfo()以下涉及/proc/meminfo中字段的直接用其顯示的字符串代指。
3.1.1 Total RAM
對應 MemTotal
3.1.2 Free RAM
包括 cached pss , cached kernel, free 三個部分。 - cached pss 對應變量cachedPss的值。 這部分進程占用的內存并沒有被釋放,而由于他們都已切換到后臺,且adj較低,系統認為可以釋放掉這部分內存。所以對于這部分進程,系統最好有機制能及時清理掉從而釋放內存。 - cached kernel 對應 Buffers + Cached + SReclaimable - Mapped 這部分的內存由于理論上是可以被Kernel回收的,所以這里也計算在free中,但是這是一個理論上的值,實際上很難做到全部回收。 - feee 對應 MemFree
3.1.2 Used RAM
包括 used pss , kernel兩個部分。 - used pss 對應兩個變量的差值,即totalPss - cachedPss。
- kernel 對應 Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack 其中VmallocUsed 是統計/proc/vmallocinfo中除ioremap,map_lowmem,vm_map_ram之外的和,詳細見Debug.get_allocated_vmalloc_memory() 這部分即是對kernel的內存占用的一個統計,如果要統計kernel的內存占用,這個稍微準確一些。
3.1.3 ZRAM
- 第一個數 用變量zramtotal來代替,表示zram實際占用的物理內存,是從/sys/block/zram0/mm_stat中統計而來
- 第二個數 對應 SwapTotal - SwapFree , 是已經在swap區的內存大小
- 第三個數 對應 SwapTotal, 是整個swap區的大小
3.1.4 Lost RAM
對應MemTotal - (totalPss - totalSwapPss) - MemFree - (cached kernel) - (kernel) - zramtotal。
前面已經說過,totalPss對應展示信息下的 cached pss + used pss。 而totalSwapPss是統計所有進程所有VMA中SwapPss的頁,含義是已經換入到swap區的頁,則(totalPss - totalSwapPss)就表示未進入交換區的PSS。
Lost RAM也與 (Total RAM) - (Free RAM) - (Used RAM) - (zramtotal) + (SwapTotal - SwapFree)的值大致相對應,詳細見代碼中的計算。 Lost RAM 可以理解為內存統計的誤差,是某些部分被重復計算或沒有計算。可以參考這里。
就我的理解,kernel USED部分中VmallocUsed就存在統計不一致的問題,詳細見下一節。
思考問題:
更詳細的影響Lost RAM的因素有哪一些呢?如何讓Lost RAM的誤差更小呢?留待后續進一步追查。
3.1.5 kernel的統計誤差
上面說到kernel的內存占用包含Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack這幾個部分,而VmallocUsed 是統計/proc/vmallocinfo輸出中不含有ioremap,map_lowmem,vm_map_ram關鍵字的內存區間之和,詳細見Debug.get_allocated_vmalloc_memory()。
//frameworks/base/core/jni/android_os_Debug.cpp static long get_allocated_vmalloc_memory() { }VmallocUsed在這里會有一些統計上的誤差,這個誤差就造成了kernel的統計誤差。
VmallocUsed對/proc/vmallocinfo的統計誤差主要表現在以下方面,詳細看2.2節。
- Kernel code 的重復統計
此處應該是 Android 的一個統計bug, kernel code的內存占用,既統計在了memblock reserved部分,又統計在了VmallocUsed部分。
- Binder的占用誤差
binder占用的內存,在進程PSS中已經統計,又統計在了kernel中,而且統計的是整個VMA區間而非實際物理占用,這造成了統計上的誤差。
3.2 進程PSS情況
是對系統所有用戶態進程PSS情況的統計,統計的基礎是以pid獲取每個進程的PSS占用情況,調用到的方法是Debug.getMemoryInfo(),最終是統計/proc/pid/smaps下的數據。各類型的內存占用信息保存在 Debug.MemoryInfo 對象中。
詳細的單個進程的內存占用統計可以查看 第四章。
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.javaDebug.MemoryInfo mi = new Debug.MemoryInfo();......//調用到JNI中的 android_os_Debug_getDirtyPagesPid() --> read_mapinfo()//具體就是解析 /proc/pid/smaps 中的內存區間,按類型匯總信息Debug.getMemoryInfo(pid, mi);一個進程的PSS就是這些信息的相加:
//frameworks/base/core/java/android/os/Debug.java public int getTotalPss() {return dalvikPss + nativePss + otherPss + getTotalSwappedOutPss(); }public int getTotalSwappedOutPss() {return dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss; }1、Total PSS by process
Total PSS by process下是由大到小顯示所有進程的PSS情況。
2、Total PSS by OOM ADJ
Total PSS by OOM adjustment下是以進程的OOM ADJ值(/proc/pid/oom_score_adj, 范圍在[-1000,1000])的區間分類來統計進程的PSS。ADJ的區間與類別對應如下:
對于Cached的進程,其ADJ很小(<900),這部分進程的內存占用計算到了Free RAM中,即系統認為這部分進程是可以回收的。
static final int[] DUMP_MEM_OOM_ADJ = new int[] {ProcessList.NATIVE_ADJ, //ADJ在此區間的為 “Native”ProcessList.SYSTEM_ADJ, //ADJ在此區間的為 “System” ProcessList.PERSISTENT_PROC_ADJ, //ADJ在此區間的為 “Persistent"ProcessList.PERSISTENT_SERVICE_ADJ,//ADJ在此區間的為 “Persistent Service"ProcessList.FOREGROUND_APP_ADJ, //ADJ在此區間的為 “Foreground"ProcessList.VISIBLE_APP_ADJ, //ADJ在此區間的為 “Visible"ProcessList.PERCEPTIBLE_APP_ADJ, //ADJ在此區間的為 “Perceptible"ProcessList.BACKUP_APP_ADJ, //ADJ在此區間的為 “Backup"ProcessList.HEAVY_WEIGHT_APP_ADJ, //ADJ在此區間的為 “Heavy Weight"ProcessList.SERVICE_ADJ, //ADJ在此區間的為 “A Services"ProcessList.HOME_APP_ADJ, //ADJ在此區間的為 “Home"ProcessList.PREVIOUS_APP_ADJ, //ADJ在此區間的為 “Previous"ProcessList.SERVICE_B_ADJ, //ADJ在此區間的為 “B Services"ProcessList.CACHED_APP_MIN_ADJ //ADJ大于此值的為 “Cached" };3、Total PSS by category
Total PSS by category下是以內存占用的類型分類來統計進程的PSS。
4、從進程角度看
這里說的是用戶態進程的內存占用,其實內核線程也有內存占用,只是Linux/Android并未進行統計,內核線程的內存占用分散在Kernel的各類占用里,比如KernelStack,Slab等 。
幾個概念: 查看一個進程的內存占用,需要弄清楚以下幾個概念: - VSS (Virtual Set Size) 虛擬耗用內存(包含共享庫占用的內存,以及分配但未使用內存) - RSS (Resident Set Size) 實際使用物理內存(包含共享庫占用的全部內存) - PSS (Proportional Set Size) 實際使用的物理內存(比例分配共享庫占用的內存) - USS (Unique Set Size) 進程獨自占用的物理內存(不包含共享庫占用的內存)
一般來說內存占用大小有如下規律:VSS >= RSS >= PSS >= USS
PSS和USS反應進程的內存占用比較有意義,PSS是按進程數比例分配共享庫內存,而USS是不包括共享庫內存,當一個進程被銷毀后,RSS是真實返回給系統的物理內存。 具體可以參考這里
4.1 dumpsys meminfo pid
dumpsys meminfo pid 是顯示指定pid進程的PSS內存占用詳細信息,這里說明不接任何參數的情況。
獲取指定pid進程的內存占用信息是通過Debug.getMemoryInfo(pid, mi)讀取整理/proc/pid/smaps下的數據,而展示這些信息是在 ActivityThread.dumpMemInfoTable()中。
4.1.1 /proc/pid/smaps
/proc/pid/maps 展示指定pid進程下的虛擬地址空間分布,而/proc/pid/smaps則是對每一虛擬地址區間(VMA)更詳細的展示。
具體代碼在:
//fs/proc/task_mmu.c static int show_smap(struct seq_file m, void v, int is_pid)使用如下的調用關系遍歷一個VMA的所有頁框(page frame)對應的struct page,統計的信息填充到 struct mem_size_stats結構體。
walk_page_vma(vma, &smaps_walk) --> __walk_page_range() --> walk_pgd_range()以下面例子說明一個VMA展示字段的含義:
70d98c8000-70d98f0000 r-xp 00000000 fc:00 493 /system/lib64/vndk-sp-28/libhwbinder.so //VMA名稱,這個關鍵字決定了這段VMA的類型 Size: 160 kB //該VMA占用的虛擬地址空間大小 Rss: 132 kB //實際占用的物理頁 Pss: 3 kB //獨占頁+按比例分配的共享頁 Shared_Clean: 132 kB //共享頁(比如共享庫使用到的頁)中符合 PageDirty(page) 的頁 Shared_Dirty: 0 kB //共享頁(比如共享庫使用到的頁)中不符合 PageDirty(page) 的頁 Private_Clean: 0 kB //獨占頁中符合 PageDirty(page) 的頁 Private_Dirty: 0 kB //獨占頁中不符合 PageDirty(page) 的頁 Referenced: 132 kB //符合PageReferenced(page)的頁 Anonymous: 0 kB //符合PageAnon(page)的頁 AnonHugePages: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB //swap的頁 SwapPss: 0 kB //swap的頁按比例分配 KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB/proc/pid/smaps展示的所有VMA的詳細信息是 dumpsys meminfo pid 顯示的基礎。
4.1.2 dumpMemInfoTable
將PSS的占用分為三大類: - nativePss 是native部分的PSS,對應Debug.MemoryInfo.nativePss,對應HEAP_NATIVE類型. /proc/pid/smaps中包含"[heap]","[anon:libc_malloc]"關鍵字的VMA都計作nativePss - dalvikPss 是dalvik部分的PSS,對應Debug.MemoryInfo.dalvikPss,對應HEAP_DALVIK類型. /proc/pid/smaps中包含"/dev/ashmem/"開頭部分關鍵字的VMA計作dalvikPss,具體見read_mapinfo()。 - otherPss 是除native和dalvik部分之外的PSS,對應Debug.MemoryInfo.otherPss,對應HEAP_UNKNOWN類型,也對應OTHER_DALVIK_OTHER到OTHER_OTHER_MEMTRACK的17個子類型. 這17個子類型都對應/proc/pid/smaps中VMA名稱的關鍵字,具體見read_mapinfo()。
每一類型的Pss,又細分為下面這些部分,都是根據/proc/smaps下各VMA中頁類型統計而來。 - xxxPrivateDirty(Private_Dirty) - xxxSharedDirty(Shared_Dirty) - xxxPrivateClean(Private_Clean) - xxxSharedClean(Shared_Clean) - xxxSwappedOut(Swap) - xxxSwappedOutPss(SwapPss)
比如getTotalSwappedOutPss()就等于dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss。
dumpsys meminfo pid 默認的輸出主要有下面兩個部分: 1、PSS Summary - Native Heap 對nativePss的展示,還包括堆的情況: Heap Size, Heap Alloc, Heap Free. 調用了 mallinfo() --> je_mallinfo()獲取navite 堆的信息。 - Dalvik Heap 對dalvikPss的展示,還包括堆的情況: Heap Size, Heap Alloc, Heap Free. 調用了Runtime.totalMemory() 和 Runtime.freeMemory()來獲取dalvik 堆 Heap Size 和 Heap Free的信息。 - Other 對otherPss下17個子類型Pss的展示,比如 Dalvik Other 對應類型OTHER_DALVIK_OTHER, 對于字段全位0的每一展示出來。 - Total 對上面所有部分的相加。
2、App Summary - Java Heap: dalvikPss 和 OTHER_ART 類型的PSS中PrivateDirty 部分之和。 - Native Heap: nativePss 中 PrivateDirty 部分。 - Code: OTHER_SO,OTHER_JAR,OTHER_APK,OTHER_TTF,OTHER_DEX,OTHER_OAT類型的PSS中PrivateDirty 部分之和。 - Stack: OTHER_STACK 類型的PSS中的 PrivateDirty 部分。 - Graphics: OTHER_GL_DEV,OTHER_GRAPHICS,OTHER_GL類型的PSS中PrivateDirty 部分之和。 - Private Other: 見 Debug.getSummaryPrivateOther() - System: 見 Debug.getSummarySystem()
4.2 procrank
涉及的代碼如下:
system/extras/procrank/ system/extras/libpagemap/procrank 主要展示所有用戶態進程的VSS/RSS/PSS/USS情況,用到的信息是 /proc/pid/maps 下的進程地址區間, 以及 使用/proc/pid/pagemap來得到物理頁使用情況。
int main(int argc, char *argv[]) {....std::vector<proc_info> procs(num_procs);for (i = 0; i < num_procs; i++) {....//pm_process_t *proc;//解析/proc/pid/maps下的進程地址區間信息,保存到proc->map[]數組中error = pm_process_create(ker, pids[i], &proc);....//將 proc->map[]下的VMA信息轉換為 procs[i].usage 信息error = pm_process_usage_flags(proc, &procs[i].usage, flags_mask, required_flags);....}//展示的VSS,RSS,PSS,USS 分別用到了 proc.usage.vss,proc.usage.rss,proc.usage.pss,proc.usage.uss }4.2.1 /proc/pid/maps
/proc/pid/maps 展示指定pid進程下的虛擬地址空間分布,每一行對應一段虛擬地址空間,在Kernel 中對應一個 struct vm_area_struct結構,稱為一個VMA(virtual memory area)。
一行信息包括該VMA的起始地址,結束地址,權限,偏移量,路徑等。示例如下:
70d965c000-70d9662000 r--p 000fa000 fc:00 523 /system/lib64/libc.so 7fc89fe000-7fc8a1f000 rw-p 00000000 00:00 0 [stack]4.2.2 /proc/pid/pagemap
從 /proc/pid/maps得到所有的VMA之后,還需要知道VMA對應的物理內存情況,因為進程分配一個VMA后,只是得到了一段虛擬地址空間,只有在真正使用內存時才會分配對應的物理內存。
/proc/pid/pagemap 就是用來查詢一個VMA的物理內存情況。 seek到 /proc/pid/pagemap虛擬地址區間的起始位置,就能讀到該段線性地址區間的物理頁情況,每一個64位int描述了一個物理頁的情況。詳細見pagemap文檔。
pagemap具體實現代碼在:
//proc/task_mmu.c static ssize_t pagemap_read(struct file file, char __user buf, size_t count, loff_t *ppos)procrank 流程中關鍵的轉換 pagemap 就是在 pm_map_usage_flags()中,從其中可以看出VSS/RSS/PSS/USS的具體差異。
//map:描述一個VMA int pm_map_usage_flags(pm_map_t *map, pm_memusage_t *usage_out,uint64_t flags_mask, uint64_t required_flags) {//使用 /proc/pid/pagemap 得到該VMA映射的物理頁的信息,//每一個物理頁的情況是一個uint64_t,保存在pagemap[]中,len是該VMA所占的頁數error = pm_map_pagemap(map, &pagemap, &len);....//遍歷該VMA下的每一物理頁(pagemap[i])for (i = 0; i < len; i++) {//只要分配了地址空間都計算在VSS中,無論物理上是否分配(物理頁不存在)usage.vss += map->proc->ker->pagesize;//如果該物理頁不存在或者該物理頁沒有被swap,則繼續if (!PM_PAGEMAP_PRESENT(pagemap[i]) && !PM_PAGEMAP_SWAPPED(pagemap[i]))continue;....//通過該頁PFN得到該物理頁被映射的次數count,通過/proc/pid/kpagecount節點error = pm_kernel_count(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),&count);....//只要映射過,該頁就計算在RSS中usage.rss += (count >= 1) ? map->proc->ker->pagesize : (0);//按映射的次數比例分配該頁的大小,計算在PSS中usage.pss += (count >= 1) ? (map->proc->ker->pagesize / count) : (0);//只有被映射過一次,才計算在USS中,即映射多次的頁(比如共享庫)比計算在USS中usage.uss += (count == 1) ? (map->proc->ker->pagesize) : (0);....} }留個問題: 為什么 dumpsys meminfo pid 顯示的進程PSS大小,比procrank展示的進程PSS大小多? 是多統計了swap?
5、總結
回應一下文本開頭所提的問題。
1、為什么/proc/meminfo中的內存總大小比物理內存小?/proc/meminfo中的MemTotal相比物理內存少了兩個部分:
- kernel進入前預申請的部分,此時memblock還未初始化。這部分的大小可以統計 /d/memblock/memory相對物理內存少的部分。
- kernel reserved的部分。這部分內存的大小可以統計/d/memblock/reserved中的各部分之和。
2、怎么看Android還剩多少可用內存比較準確?
查看dumpsys meminfo中 Free RAM 的部分相對準確, 而/proc/meminfo中的MemAvailable只是一個理論上通過回收能達到的最大值,實際上很難達到。
3、怎么看Kernel的內存占用比較準確?
查看dumpsys meminfo中 Used RAM下的kernel部分的大小,它也是/proc/meminfo中下面部分的和: Shmem + SUnreclaim + VmallocUsed + PageTables + KernelStack。 但是這部分的統計比實際占用要多,主要在VmallocUsed的統計,詳細看 3.1.5節
4、是哪些因素影響了Lost RAM的大小?
有一個因素是Android 對 kernel的內存占用統計存在偏差,這有體現在對/proc/vmalloc中內存區間的統計。
5、怎么看一個進程的內存占用比較合適?
一個進程的內存占用有VSS/RSS/PSS/USS之分,USS是進程的獨占物理內存大小,PSS則是USS加上按比例分配的共享庫內存,相對來說PSS更加合適。 通過dumpsys meminfo pid查看指定進程的PSS情況。
總結
以上是生活随笔為你收集整理的windows2008开机占用多少内存_Android内存占用分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hca卡 linux 查看_将Linux
- 下一篇: 代表:大学生没必要都在大城市卷 可以选择