目录
命令:adb shell cat /proc/meminfo
procrank 分解及VSS/RSS/PSS/USS基础知识
/proc/meminfo memory状态解读
命令:adb shell cat /proc/meminfo
用途:用于整体的了解memory使用情况
通常说的可用memory一般以MemAvailable的数据为准,所以了解MemAvailable的组成可以帮助理解如何提升可用内存:
MemAvailable= free+filepage +slab_reclaimable -(lowmem_reserve +high_wmark_pages) -min(filepage/2,wmark_low) - min(slab_reclaimable/2,wmark_low)
其中:
lowmem_reserve为cat /proc/zoneinfo中的protection数据, 一般获取到的数据为0: protection: (0, 0, 0)
high_wmark_pages 和 wmark_low 也是从zoneinfo中获取的high/low 水位
所以上述公式可以理解为: free, filepage, slab_reclaimable越大,且high_wmark_pages, wmark_low越小 ,则MemAvailable越多
Name | 释义 | Action |
---|---|---|
MemTotal |
系统可用的总内存 (MemTotal = real_Dram_size - hw_reserved - kernel_reserved ) |
|
MemFree |
当前剩余的物理内存总量 free跟process数据有关,就算有被压缩,但其还是会mapping相关的memory,造成free比较少 |
如果cached+MemFree <200M可能就会出现memory使用紧张的问题 |
MemAvailable |
系统可用内存 , 包含已使用但可回收的部分 , 如MemAvailable = free+filepage +slab_reclaimable -(lowmem_reserve +high_wmark_pages) -min(filepage/2,wmark_low) - min(slab_reclaimable/2,wmark_low) 参见code: si_mem_available 其中lowmem_reserve为/proc/zoneinfo中的protection信息。 |
1、如果刚开机未达到开机可用内存目标,按照如下几个步骤进行检查; 1.1、检查 本表格中MemTotal 是否达到预期 1.2、检查本表格中anon,file的用量比 1.3、检查kernel memory分解, 排查kernel使用异常部分,并优化 1.4、检查本页“dumpsys meminfo分解”部分排查某个或某些内存占高的进程并优化 2、若是运行过程中出现low memory 问题, 则可以从降低单个process的memory使用量以及减少后台process的存活数量来着手,加强后台管理。 |
Buffers |
Buffer是为了cpu和块设备(block device)之间读写速度不对等而设计的,Buffers统计的就是这部分缓冲区的内存总大小。这部分内存drop cache可以被回收。 |
|
Cached |
用于文件高速缓存,不包括swapcache和buffers 即Cached = file pages-swapcache-buffers 约等于 Active(file) + Inactive(file) cached统计了文件的缓存,其中有些文件当前被unmap, cache仍然可能保留它们,该部分可以被drop cache直接回收;而还有一部分被用户进程关联,比如shared libraries,mmap的文件等,这些缓存也就称为mapped. mapped是包含在cached里面,这部分是不可以被drop caches的. cached都是file page,不是anon page, cached 和anon page之间没有重叠。 |
cached的大小一定程度上决定了MemAvailable的大小。所以若cached太小,请check anonpage和file page的比例 |
SwapCached |
缓存的会swap出去的内容 |
|
Active |
活跃的file page和匿名page Active = Active(anon) + Active(file) 记录最近使用过的内存,通常不回收用于其它目的 |
|
Inactive |
非活动的file page和匿名page Inactive = Inactive(anon) + Inactive(file) 记录最近并没有使用过的内存,能够被回收用于其他目的 |
|
Active(anon) |
活跃的匿名页 |
如果anon数据较多,而file文件较少,请继续check swap Total和 SwapFree两个参数 |
Inactive(anon) |
不活跃的匿名页 |
|
Active(file) |
活跃的文件页 |
|
Inactive(file) |
已经使用的不活跃的文件页 |
|
Unevictable |
已经使用的不可回收的页. 包括VM_LOCKED的内存页、SHM_LOCK的共享内存页(又被统计在”mlocked”中)、和ramfs。 |
|
Mlocked |
通过mlock锁定在内存中当中的空间,不会被放到swap space当中 |
|
SwapTotal |
swap分区的总量 |
1、针对低端机,可以适当加大swaptotal,如2G RAM 可以将其设置到1.3G左右 2、如果swapFree还剩余很多,且anon数据量大,请查看swappiness 的设置,并加大其设定的参数: adb shell cat /proc/sys/vm/swappiness 3、如果swapFree已经所剩无几,说明当前process较多,或者process所占内存较大。如果当前已处于低内存状态,请查看本页“dumpsys meminfo分解”部分排查某个或某些内存占高的进程并优化 |
SwapFree |
swap分区目前可用的量 |
|
Dirty |
脏页的数量(就是文件内容有改变的页) 在磁盘缓冲区中尚未写入物理磁盘的内存大小 |
|
Writeback |
要被回写的内存页的大小 |
|
AnonPages |
未映射文件的内存页数量 Linux内核中存在一个rmap(reverse mapping)机制,负责管理匿名内存中每一个物理内存页映射到哪个进程的哪个逻辑地址这样的信息。 这个rmap中记录的内存页总和就是AnonPages的值 |
|
Mapped |
文件通过mmap分配的内存,用于map设备、文件或者库 |
|
Shmem |
被各个进程共享的内存页的数量 tmpfs所使用的内存.tmpfs即利用物理内存来提供RAM磁盘的功能。在tmpfs上保存文件时,文件系统会暂时将它们保存到磁盘高速缓存上,因此它是属于磁盘高速缓存对应的"buffers+cached"一类。。但是由于磁盘上并没有与之对应的内容,因此它并未记录在File-backed内存对应的LRU列表上,而是记录在匿名内存的LRU表上。 这就是 buffers + cached = Active(file) + Inactive(file) + Shmem 公式的由来 |
被统计如kernel 占用内存 |
Slab |
kernel数据结构的缓存大小,Slab=SReclaimable+SUnreclaim Slab allocator是Linux kernel的内存分配机制,各内核子系统、模块、驱动程序都可以使用,但用完应该记得释放,忘记释放就会造成“内存泄露”(memory leak)。如果导致泄露的代码使用率很低倒也罢了,若是使用率很高的话,系统的内存会被迅速耗尽。 |
|
SReclaimable |
Slab可被回收的量。 调用kmem_getpages()时加上SLAB_RECLAIM_ACCOUNT标记,表明是可回收的,计入SReclaimable,否则计入SUnreclaim。 |
|
SUnreclaim |
Slab中不可回收的量 |
|
KernelStack |
内核线程栈占用的空间 |
|
PageTables |
该进程页表所占用的memory |
|
NFS_Unstable |
不稳定页表的大小 |
|
Bounce |
有些老设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O 请求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为”bounce buffering”,会降低I/O 性能。大量分配的bounce buffers 也会占用额外的内存。 |
|
WritebackTmp |
|
|
CommitLimit |
指当前可以分配给程序使用的虚拟内存 |
|
Committed_AS |
指当前已分配给程序使用的总虚拟内存 |
|
VmallocTotal |
总分配的虚拟地址空间 |
|
VmallocUsed |
vmalloc已经使用的内存量 |
|
VmallocChunk |
当前剩余的最大连续空间的大小 |
内存分布log 查看方式
kernel log:
<4>[366310.867958] DMA free:68160kB min:5140kB low:44156kB high:45996kB active_anon:126592kB inactive_anon:126772kB active_file:211508kB inactive_file:185592kB unevictable:5380kB writepending:1896kB present:1988536kB managed:1841596kB mlocked:5380kB slab_reclaimable:40648kB slab_unreclaimable:187980kB kernel_stack:49760kB pagetables:78368kB bounce:0kB free_pcp:2844kB local_pcp:696kB free_cma:0kB
- DMA free:68160kB
- 总剩余内存
- min:5140kB low:44156kB high:45996kB
- min/low/high 三个水位值,其释义可以参考Linux内存调节之zone watermark
- active_anon:126592kB inactive_anon:126772kB
- userspace process memory
- active_file:211508kB inactive_file:185592kB
- userspace file cache memory
- managed:1841596kB
- DRAM total减掉 reserved memory ,系统总共可运用的内存大小
- slab_reclaimable:40648kB slab_unreclaimable:187980kB
- slab 占用内存
- kernel_stack:49760kB
- kernel stack 占用内存, 可用来计算process数量 (32bit 8k per process , 64bit 16k per porcess)
- 注意ion 和 gpu 无法由此看出, 可以通过本页“dumpsys meminfo分解”部分进一步确认
dumpsys meminfo分解
dumpsys meminfo
描述:
用于了解系统详细的memory使用的情况,通过dumpsys meminfo 可以得到系统中各个process占用的内存情况,各种adj情况下的process的内存分布,总体的memory的使用统计等信息。区别于/proc/meminfo提供的内存数据,dumpsys meminfo更加偏向于andorid系统对于系统中memory使用情况的理解,因此我们在实际的memory的debug的过程中,在android系统更倾向使用dumpsys meminfo命令
执行结果:
名称 |
计算方式 |
next action |
Total PSS by process |
将系统中run的process的内存信息打印出来,通过该部分信息,可以清楚的看到每个process的内存使用情况 |
在实际debug的过程中,如果怀疑某个process内存使用异常或者有leak的嫌疑,我们会以这个里面提供该process为基准进行相应的判断。。如果某个process的memory占用过大,或者一直增长,则可以通过dumpsys meminfo pid 来进一步check. |
Total PSS by OOM adjustment |
andorid系统的继承了部分linux 内存管理的做法,会在系统内存比较紧张或者在某些内存长期不使用的情况,会清掉优先级较低的进程,来让系统的具有良好的内存使用状况。对android系统我们的会对每个process进行打分,分数越高表示优先级越低,在内存紧张的时候就越容易被回收。 |
在实际debug的过程中,时常会遇到系统因为memory紧张导致重启的case,这种情况大都是native process 或者kernel里面有leak导致的。kernel 里面的leak大都是driver不合理的memory 使用导致的可以使用page owner来抓,判断native的process是否有leak,就是可以根据上述的native process里面是否有使用内存异常的process来判断。 |
total PSS |
cached pss + used pss (cached pss : adj >=900 process pss sum , used pss : adj <900 process pss sum) |
检查是否有占用memory异常多的process,然后再根据本文第二部分的dumpsys meminfo $pid的内容进一步确认单个process memory 占用异常的问题 |
total kernel |
cached kernel + kernel |
确认kernel部分的占用情况 |
cached kernel |
slab reclaimable + buffers + cached - mapped |
|
kernel |
slab unreclaimable + shmem + vmalloc + page tables + kernel stack |
|
EGL/GL |
ION/GPU内存占用,可参考本文第二部分的dumpsys meminfo $pid 中graphics部分 |
ION部分请参考 DMABUF/EGL/ION |
dumpsys meminfo $pid
名称 |
计算方式 |
如果相应值偏大,next Action |
code |
.so private (clean+dirty) + .jar private (clean+dirty) + .apk private (clean+dirty) + .ttf private (clean+ dirty) + .dex private (clean + dirty) + .oat private (clean + dirty) |
1、参考dumpsys 上面各个组成部分,定位是哪部分数据过大:.so/.jar/.apk/.ttf/.dex/.oat? 2、参考下一章节“maps/smaps/showmap”,使用showmap/smaps再具体定位是哪个文件,然后再请相应owner进一步确认或优化 |
Java heap |
Dalvik private dirty+.art mmap private clean+.art mmap private dirty |
抓取hprof 排查 heap 分布 |
graphics |
GL mtrack private (clean + dirty) + EGL mtrack private(clean + dirty) |
|
native heap |
native private dirty |
|
private other |
private clean列+private dirty列-java heap-native heap-code-stack-graphic |
|
PSS total |
Pss Total列总和 + SwapPss Dirty列总和 |
|
stack |
stack private dirty |
|
system |
TOTAL -(private Clean 列总和+private dirty列总和) |
其中 .so,.jar,.apk,.ttf,.dex,.odex,.vdex,.oat,.art都是is_swappable的,其函数实现可参考:
/frameworks/base/core/jni/android_os_Debug.cpp function: load_maps
Pss、Shared Dirty、Private Dirty这三列的数据是读取smaps文件生成。
maps/smaps/showmap
Linux采用虚拟内存技术管理process 地址空间,将process memory分成不同的内存区。每个区域具有各自的访问属性,大小是4k的倍数。
代码实现上使用vm_area_struct节点串成链表。我们可以从maps & smaps里面获取到process detail的memory信息。
参考link: kernel document https://www.kernel.org/doc/Documentation/filesystems/proc.txt
maps
adb shell cat /proc/pid/maps
该文件有6列:
地址(09e3b000-09e3d000):地址范围
权限(r--p):虚拟内存的权限,r=读,w=写,x=执行,s=共享,p=私有;
偏移量(00000000):在进程里地址偏移量
设备(fd:07):映像文件的主设备号和次设备号,可以通过通过 cat /proc/devices查看设备号对应的设备名
节点(27174908):映像文件的节点号;
路径(/system/bin/app_process32): 映像文件的路径
每项都与一个vm_area_struct结构成员对应。
smaps
adb shell cat /proc/pid/smaps
smaps是对maps 信息的详细描述。描述了每个虚拟内存区域的size等信息。
e8934000-e8938000 r--p 00070000 fd:07 67146785 /system/lib/libc++.so // 该行同maps中的信息
Size: 16 kB //指vss, 以及后面RSS PSS释义请参见后续“procrank 分解及VSS/RSS/PSS/USS基础知识”的说明
Rss: 16 kB
Pss: 2 kB
Shared_Clean: 0 kB //映射中已被此进程引用的页面,以及至少一个其他进程,但不是由任何进程编写的;
Shared_Dirty: 16 kB //映射中已被此进程引用的页面,并且至少由其中一个进程编写; 其中dirty页如果没有交换机制的情况下,应该是不能回收的
Private_Clean: 0 kB //映射中已读取但未由此进程写入但未被任何其他进程引用的页面;
Private_Dirty: 0 kB //映射中已由此进程写入但未被任何其他进程引用的页面.
Referenced: 16 kB //表示当前标记为已引用或者已访问的内存量
Anonymous: 16 kB //不属于任何文件的内存量
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
Swap: 0 kB //显示多少潜在的已经被使用但没有被交换的匿名内存内存页
KernelPageSize: 4 kB //kernel用来支持虚拟内存区域的页面大小。一般与MMU使用大小相匹配。
MMUPageSize: 4 kB //被MMU使用的页面大小
Locked: 0 kB //表示映射是否被锁定在内存中
ProtectionKey: 0
VmFlags: rd ex mr mw me dw
根据smaps中每一项的object内容,可以把memory分成不同的category,借助其他统计工具可以统计出每种类型Memory的用量。
.so |
cmdline: => contains .so native lib. RO + RW |
boot.oat + boot.art |
cmdline: => contains .oat or contains .art preloaded classes which are commonly used by multiple apps, 例如:core-libart.jar, conscrypt.jar, okhttp.jar, core-junit.jar, bouncycastle.jar, ext.jar, framework.jar telephony-common.jar, android.policy.jar, apache-xml.jar |
dex |
cmdline: => contains dex Process本身依赖的一些java模块对应的dex |
Native (Heap) |
cmdline: => contains heap, contains libc_malloc, contains linker_alloc, or contains sigpage |
stack |
cmdline: => contains stack and does not contain anon |
anon |
cmdline: => contains anon unknown |
Java |
cmdline: => contains dalvik Java Heap. 由App本身需要的Heap以及Zygote所占用的Heap两部分组成。 |
Others |
perm: => contains w or contains x |
copy-on-write mapping |
perm: => ---p |
Resource |
其他不知如何归类的认为是resource. 例如font, apk等 |
showmap
adb shell system/bin/showmap $pid
功能:可以用来定位占用memory比较大的资源模块。定位后可以根据实际情况找相关模块确认是否合理。
我们一般用PSS+SwapPss来的求和来计算该文件占用的memory大小。
showmap和smaps的关系
实际是读取上述smaps中的数据然后规整分类,测试中showmap和cat/proc/PID/smaps抓取要连续,间隔久了memory会有变化
showmap中看到的是smaps中对应binary r--p/--xp/rw-p/rw-p所有项的聚合,是share lib size or execuatable size;
smaps中--xp是code size, 其他项都是data size。
procrank 分解及VSS/RSS/PSS/USS基础知识
procrank指令
功能: 获取所有进程的内存使用的排行榜,排行是以Pss
的大小而排序。
procrank
命令比dumpsys meminfo
命令,能输出更详细的VSS/RSS/PSS/USS内存指标。
其数据是从proc/[pid]/smaps中统计出来的,
注意: 使用前需先 root
执行结果:
VSS/RSS/PSS/USS基础知识
process memory 占用量
1. VSS
也写做VIRT或VSZ,它是进程使用的虚拟内存总量,不管是否有映射了物理内存。VSS在确认单个进程实际内存使用大小过程中用处不大。
比如malloc 一块大内存,实际上只分配了虚拟地址,并没有映射(消耗)物理地址,只有对这块内存访问时(比如 memeset)才会去映射物理内存。
Vss会高估实际内存使用量,因为程序通常会分配一些内存,但可能从来没用过。
Process 虚拟地址空间处理
当Process通过exec()类系统调用开始某个Program的执行时,Kernel 分配给Process的虚拟地址空间由以下内存区组成:
- program的可执行代码
- program的初始化数据
- program的未初始化数据
- 初始程序stack
- 共享库的可执行代码和数据
- heap
Kernel采用请求调页(demand paging)的内存分配策略。process可以在它的页还没在内存时就开始执行.
当process 访问一个不存在的页时,MMU产生一个异常;异常处理程序找到受影响的内存区,分配一个空闲的页,并用适当的数据进行初始化。
同样,process调用malloc 动态请求内存时,内核仅修改进程的堆内存区的大小。只有试图引用process的虚拟地址而产生异常时,才给进程分配页框
2. RSS
也可以写作RES或者RSS, 这个是进程映射的物理内存数量。 用Rss来计算程序内存使用量,可能稍微好些,但是还是会有高估:因为它没有考虑到进程间共享的内存。
比如libc.so 在系统中只有一份内存copy,但是每个进程的Rss 都会把libc.so link到各进程的空间计算在内。
3. PSS
Pss 中除了独享的内存外,还会加上平均共享内存(共享的内存 除以 共享的进程数量,所以是平均值,也并不精确,占用多少跟共享内存大小及共享进程的数量有关)
PSS 是一个非常有用的数字,因为系统中全部进程以整体的方式被统计, 对于系统中的整体内存使用是一个很好的描述。
如果一个进程被终止, 其PSS 中所使用的共享库大小将会重新按比例分配给剩下的仍在运行并且仍在使用该共享库的进程。
此种计算方式有轻微的误差,因为当某个进程中止的时候,PSS没有精确的表示被返还给整个系统的内存大小。
4. USS
Uss只统计进程的独享内存, 不包含与其他进程共享的内存。
比如A 和 B 进程都link 了libc.so, 那么A 或B进程统计Uss时,将把libc.so 占用的内存去掉。
Uss是一个非常非常有用的数据,因为它揭示了运行一个特定进程的真实内存增量大小
如果进程被终止,USS就是实际被返还给系统的内存大小。
USS是针对某个进程开始有可疑内存泄露的情况,进行检测的最佳数据。
5. 关系
我们假如进程的内存包括:
- A = 映射到物理内存的共享内存,这个会包含部分active的stack空间和heap空间。
- B = 进程间映射和共享空间,比如shared libraries
- C = 分配但是从未touch的虚拟地址空间。
那么对于每个Process:
Vss = A + B + C
Rss = A + B
Uss = A
Pss = A + B/n , n是共享这个内存的Process的数量
假如系统中有如下3个process,使用内存如下:
Pss(1) = 2 + 3/3 + 2/2 = 4
Pss(2) = 2 + 3/3 + 2/2 = 4
Pss(3) = 2 + 3/3 = 3
Sum(Pss) = 11 = 系统所有进程实际使用的物理内存
6. 题外篇
shared total :Tshare = Rss - Uss
share memory average: Ashare= Pss- Uss
通过上述数据可以获得该shared memory 区域的进程数 Nprocess= Tshare/Ashare
swap/PSwap/USwap/ZSwap
swap, 表示总共交换出去的size,和Rss相对应,可以忽略,用处不大
PSwap, pss中交换出去的size,和pss相对应(比例分配共享库占用的内存)。
USwap, 对应Uss,进程独自占用的物理内存中交换出去的size。
ZSwap, 使用zram 使用的大小。