目录
内存统计工具
/proc/meminfo
可以提供整体内存信息,各字段表示的意思如下:
字段 |
表示意思 |
MemTotal |
系统可以使用的总内存 |
MemFree |
buddy 里面所有 free 的 page 的数(不包括冷热页,目前也没有看见哪里统计 有冷热页) |
Buffers |
块设备的缓存页(不属于某个具体文件的管理信息的缓存页) |
Cached |
属于具体某个文件的缓存页 |
SwapCached |
swap cache 中的缓存页,其不包含在下面的 lru 中 |
Active |
系统中正在使用中的且使用的较积极的页 |
Inactive |
lru 系统中正在使用中的但是不太积极的页 |
Active(anon) |
Active 的匿名页 |
Inactive(anon) |
Inactive 的匿名页 |
Active(file) |
Active 的文件缓存页 |
Inactive(file) |
Inactive 的文件缓存页 |
Unevictable |
隔离的,暂时不参与 active/Inactive 判断的匿名页和文件缓存页之和 |
SwapTotal |
swap 分区总大小(对于 zram,是 zram 块设备的大小,而不是 zram 实际占用的大小) |
SwapFree |
swap 分区 free 空间(对于 zram,是 zram 块设备剩余空间) |
Dirty |
文件缓存中的脏页,如果这个地方很大,就要小心 IO 的配置或者 IO 有问题,比如之前有 Bug 将 dirty_ration 设置为 30%,就会出现这种情况 |
Writeback |
文件缓存中正在写回的页 |
AnonPages |
APP 所使用的匿名映射的 pages(没有被 swap 出去的部分)。基本就是 Active(anon)+ Inactive(anon) |
Mapped |
mapped 文件的部分,其应该是 cached 的一个子集 |
Slab |
slab 占有的所有page. 如果user_debug 版本打开了slub 将显著的增大该项 |
SReclaimable |
其中设置为 Reclaimable 的 slab 占用的部分 |
SUnreclaim |
设置为 Unreclaim 的 slab 占用的部分 |
KernelStack |
每个线程(无论用户线程还是内核线程)都有一个 8K(arm32)的内核空间 线程栈 |
PageTables |
用户进程的二级页表 pte 项(不包括内核进程的),所以可以认为完全是用户 态占用 |
VmallocTotal |
内核 vmalloc 可以用的全部空间 |
VmallocUsed |
已经使用了的空间(但不一定是 vmalloc 分配了物理内存的) |
VmallocChunk |
vmalloc 区域剩余的最大连续空间 |
Buddy
分别是各个Order 的块的个数。Buddy 之和就是proc/meminfo 中free 的大小。
Slub
root@XXXX:/ # cat /proc/slabinfo slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables
<limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> ext4_groupinfo_4k 61 156 104 39 1 : tunables 0 0 0 : slabdata
4 4 0
UDPLITEv6 |
0 |
0 |
768 |
21 |
4 : tunables |
0 |
0 |
0 : slabdata |
||||
0 0 |
0 |
|||||||||||
UDPv6 |
1 |
42 |
768 |
21 |
4 : tunables |
0 |
0 |
0 : slabdata |
||||
2 2 |
0 |
|||||||||||
... |
||||||||||||
kmalloc-4096 |
184 |
196 |
4096 |
4 |
4 : tunables |
0 |
0 |
0 : slabdata |
||||
49 49 |
0 |
|||||||||||
kmalloc-2048 |
95 |
136 |
2048 |
8 |
4 : tunables |
0 |
0 |
0 : slabdata |
||||
17 17 |
0 |
|||||||||||
kmalloc-1024 |
377 |
400 |
1024 |
16 |
4 : tunables |
0 |
0 |
0 : slabdata |
||||
25 25 |
0 |
|||||||||||
kmalloc-512 |
918 |
1072 |
512 |
16 |
2 : tunables |
0 |
0 |
0 : slabdata |
||||
67 67 |
0 |
|||||||||||
kmalloc-256 |
635 |
704 |
256 |
16 |
1 : tunables |
0 |
0 |
0 : slabdata |
||||
44 44 |
0 |
|||||||||||
kmalloc-192 |
4726 |
4893 |
192 |
21 |
1 : tunables |
0 |
0 |
0 : slabdata |
||||
233 233 |
0 |
|||||||||||
kmalloc-128 |
5501 |
6048 |
128 |
32 |
1 : tunables |
0 |
0 |
0 : slabdata |
||||
189 189 |
0 |
|||||||||||
kmalloc-64 |
47242 |
48640 |
64 |
64 |
1 : tunables |
0 |
0 |
0 : slabdata |
760 760 0
kmem_cache_node 76 256 64 64 1 : tunables 0 0 0 : slabdata 4 4 0
kmem_cache 76 160 128 32 1 : tunables 0 0 0 : slabdata 5 5 0
各项信息为:
<active_objs>:The number of objects (memory blocks) that are in use (allocated)
<num_objs>:总共现在有多少 memory blocks Objsize:The size of the objects.
Objperslab:每个 slab 有多少个 memory blocks Pagesperslab:每个 slab 占用多少个page(4096)。
Procrank
字段 |
表示意思 |
VSS(virtual set size) |
单个进程全部可访问的用户虚拟地址空间 |
RSS(Resident set size) |
单个进程实际占用的内存大小(所有共享库的全部内存大小) |
PSS(proportional set size) |
不同于 RSS,它只是按比例包含其所使用的共享库大小。 |
USS(Unique set size) |
是单个进程的全部私有内存大小。 |
Swap |
某个进程被交换出去的匿名页的大小就是 Swap(包括在 swap cache 中的和 真正交换到 swap 分区的都算)。 |
Pswap |
模仿 PSS 的概念,因为每个匿名页可能会被多个进程使用。因此 Swap 中 的 page 按照目前还有多少进程使用它,按照比例分配得到 pswap。 |
Uswap |
模仿 Uss 的概念,私有的且被 swap 出去的内存的大小。 |
127|root@XXXX:/ # procrank
PID |
Vss |
Rss |
Pss |
Uss |
Swap PSwap cmdline |
|||
1237 |
1128200K |
108136K |
55922K |
52196K |
1388K 1388K system_server |
|||
1390 |
732552K |
105912K |
52002K |
46884K |
52K 52K |
|||
com.android.systemui |
||||||||
1977 1028756K |
81440K |
31869K |
28868K |
0K |
0K |
|||
com.android.launcher3 |
||||||||
3474 1058820K |
75688K |
22556K |
17464K |
0K |
0K |
|||
com.sds.android.ttpod:channel |
||||||||
3343 |
1301320K |
67968K |
20864K |
16336K |
0K |
0K com.tencent.mm |
||
... |
||||||||
186 |
9172K |
1448K |
208K |
160K |
692K |
692K |
/system/bin/vold |
|
224 |
4748K |
928K |
172K |
160K |
256K |
256K |
||
/system/bin/download |
||||||||
325 |
3092K |
1100K |
165K |
20K |
0K |
0K |
/system/xbin/srtd |
|
362 |
3092K |
460K |
152K |
20K |
0K |
0K |
/system/xbin/srtd |
|
207 |
3104K |
928K |
80K |
64K |
232K |
232K |
/system/bin/lmkd |
|
252 |
3036K |
804K |
68K |
12K |
312K |
312K |
/system/bin/sh |
|
213 |
7824K |
844K |
65K |
52K |
256K |
256K |
||
/system/bin/modemd |
||||||||
218 |
988K |
96K |
64K |
64K |
56K |
56K |
/bin/batterysrv |
|
194 |
5284K |
1192K |
54K |
24K |
464K |
464K |
||
/system/bin/gatekeeperd |
||||||||
225 |
5108K |
792K |
40K |
28K |
228K |
228K |
/system/bin/gnss_download 212 4708K 792K 17K 4K 284K 284K
/system/bin/modem_control 219 4860K 748K 16K 4K 284K 284K
/system/bin/modemDriver_vpad_main 195 2800K 804K 15K 0K 308K 308K
/system/xbin/perfprofd 627540K 492360K 10772K 10619K TOTAL
RAM: 939244K total, 19076K free, 8768K buffers, 365548K cached, 4024K shmem, 34248K slab
/proc/pid/smaps
这个是对进程地址空间以及内存使用,最全面的展示。各项说明参考:
6f387000-6ff18000 rw-p 00000000 b3:15 7575 /data/dalvik-cache/arm/system@framework@boot.art
Size: |
11844 kB |
该段虚拟空间大小 |
Rss: |
11828 kB |
= Shared_Clean+ Shared_Dirty+ Private_Clean+ Private_Dirty= Filecache+ |
Anonymous Pss: |
2179 kB |
= Pss_Filecache+ Pss_Anonymous |
Uss: |
1304 kB |
= Private_Filecache+Private_Anonymou |
Shared_Clean: |
7964 kB |
和其它进程共享而且是干净的 |
Shared_Dirty: |
2560 kB |
和其它进程共享而且是脏的 |
Private_Clean: |
616 kB |
本进程私有而且是干净的 |
Private_Dirty: Referenced: Filecache: |
688 kB 11544 kB 8580 kB |
本进程私有而且是脏的 文件缓存 = Shared_Filecache+ Private_Filecache |
Pss_Filecache: |
1344 kB |
按比例共享的文件缓存 |
Shared_Filecache: |
7964 kB |
多进程共享的文件缓存 |
Private_Filecache: |
616 kB |
私有的文件缓存 |
Anonymous: |
3248 kB |
匿名内存 = Shared_Anonymous+ Private_Anonymous |
Pss_Anonymous: |
835 kB |
按比例共享的匿名内存 |
Shared_Anonymous: |
2560 kB |
多进程共享的匿名内存 |
Private_Anonymous: AnonHugePages: Swap: |
688 kB 0 kB 0 kB |
私有的匿名内存 进程被交换出去的匿名页 |
PSwap: |
0 kB |
按比例共享的被交换出去的匿名页 |
USwap: KernelPageSize: MMUPageSize: Locked: |
0 kB 4 kB 4 kB 0 kB |
私有的被交换出去的匿名页 |
VmFlags: rd wr mr mw me ac mg
Dumpsys meminfo
Dumpsys meminfo 是 android 提供的比较有用的内存信息统计工具,并且结合了进程的 adj,可以查看各组进程所占的内存情况。
各个数据的统计来自/proc/pid/smaps,对应关系请参考:
dumpsys meminfo |
smaps line |
特殊计算 |
Native Heap |
[heap] [anon:libc_malloc]XXX |
|
Dalvik Heap |
.Heap+.LOS+.NonMoving +.Zygote |
|
Dalvik Other |
.LinearAlloc+.Indirec tRef+.JITCache+.GC |
|
Stack |
[stackXXX |
|
Ashmem |
/dev/ashmemXXX (-/dev/ashmem/dalvik-XXX -/dev/ashmem/CursonrWindowXXX -/dev/ashmem/libc mallocXXX) |
Gfx dev |
/dev/kgsl-3d0XXX |
|
Other dev |
/dev/ (-/dev/kgsl-3d0 -/dev/ashmem) |
|
.so mmap |
XXX.so 共享库的 bss 段 |
|
.jar mmap |
XXX.jar |
|
.apk mmap |
XXX.apk |
|
.ttf mmap |
XXX.ttf |
|
.dex mmap |
XXX.dex XXX.odex |
|
.oat mmap |
XXX.oat |
|
.art mmap |
XXX.art |
|
Other mmap |
namelen>0,已经统计的情况除外 |
|
EGL mtrack |
||
GL mtrack |
||
Other mtrack |
||
Unknow |
[anonXXX|new mapping |
|
Dalvik Details |
||
.Heap |
/dev/ashmem/dalvik-alloc space /dev/ashmem/dalvik-main space |
|
.Los |
/dev/ashmem/dalvik-large object space |
|
.LinearAlloc |
/dev/ashmem/dalvik-LinearAlloc |
|
.GC |
/dev/ashmem/dalvik-XXX (-heap_dalvik- (.LinearAlloc,.IndirectRef,.JitCache)) |
|
.JITCache |
/dev/ashmem/dalvik-jit-code-cache |
|
.Zygote |
/dev/ashmem/dalvik-zygote space |
|
.NonMoving |
/dev/ashmem/dalvik-non moving space |
|
.IndirectRef |
/dev/ashmem/dalvik-indirect ref |
App Summary(主要是用来体现进程独占的那些内存)
Java Heap: |
Private Dirty(Dalvik Heap) + Private Clean(.art) + Private Dirty(.art) |
该进程独有 Java Heap |
这个有点问题的, .art比较复杂也会包含文件缓存的(而从经验上看Private Clean(.art) 全部是Private_Filecache; 而Private Dirty(.art) 全部 是 Private_Anonymous) |
Native Heap: |
Private Dirty(Native Heap) |
该进程独有的 Native Heap |
|
Code: |
Private(.so) + Private(.jar)+Private(.apk)+Private(. ttf)+Private(.dex)+Private(.oat) |
该进程独有的代码占用的内存 |
.so 其实并不完全是 Code,其也包 含.data, .rodat .bss。这个参考意义有限,因为mmap 部分动态变化且不好统计的 |
Stack: |
Private Dirty(Stack) |
该进程独有的栈内存 |
|
Graphics: |
Private(Gfx_dex) + Private(EGL mtrack) + Private(GL mtrack) |
||
Private Other: |
Private(Native Heap) + Private(Dalvik Heap) + Private(Unknow) -Java Heap-Native Heap - Code -Stack - Graphics |
剩余的独有的部分 |
即:TOTAL(private) - Java Heap - Native Heap - Code - Stack - Graphics(应该就是Dalvik Other+Ashmem+Unkno w) |
System: |
Pss(Dalvik Heap) + Pss(Native Heap) +Pss(Unknow)-Private(Dalvik Heap)-Private(Native Heap)-Private(Unknow) |
和其它进程共享的,就算做系统的部分。 |
即:TOTAL(Pss) -TOTAL(Private)。 就是指和其他进程有共享的,就算系统占用的PSS 部分 |
dumpsys meminfo |
smaps |
备注 |
|
Pss Total |
Pss: |
Pss Clean |
sharing_proportion=(pss-priv ate_clean-private_dirty)/(shar ed_clean+shared_dirty) sharing_proportion*shared_c lean + private_clean |
前提条件:
(shared_clean>0|shared_di rty>0) |
这个就只统计这几 部分,其它的不管。所以 heap 的都为 0 |
Shared Dirty |
Shared_Dirty: |
共享的脏的 |
|
Private Dirty |
Private_Dirty: |
独占的脏的 |
|
Shared Clean |
Shared_Clean: |
共享的干净的 |
|
Private Clean |
Private_Clean |
独占的干净的 |
|
Swapped Dirty |
Swap: |
被交换出去的 |
|
Private Swapped |
Uswap |
独有的,被交换出去的 |
|
Pss Swapped |
Pswap |
按比例被交换出去的 |
|
Heap Size(Native Heap) |
mallinfo->usmblks |
程序申请内存的最 高值 |
|
Heap Alloc(Native Heap) |
mallinfo->uordblks |
当前正在使用的内 存大小 |
|
Heap Free(Native Heap) |
mallinfo->fordblks |
当前空闲的内存大 小 |
|
Heap Size(Dalvik Heap) |
Runtime.totalMemory() |
Xms 参数指定 |
|
Heap Alloc(Dalvik Heap) |
Heap Size - Heap Free |
||
Heap Free(Dalvik Heap) |
Runtime.freeMemory() |
内存评估
有时需要评估二个版本或者同一个版本二种场景下的系统内存情况。比如某公司项目发现,插 sim 比不插 sim,性能差,想看看这二种情况下的内存情况。
首先,可以收集信息,看看同样在开机5分钟的场景下,FreeRAM 的差异。下面的数据可以从/proc/meminfo 和 dumpsys meminfo 中获得。
其次,看下这多出的内存都用在哪里了。
从上面可以,看出,二种场景主要是 framework+app 的内存差异,那进一步看下,用户空间的内存使用情况。下面数据可以由dumpsys meminfo 与 procrank得出。
由上面的数据分析,可以得出结论:
插 sim 卡时,FreeRAM 少 40M。是因为上层 app 会多,从收集的数据看,会多占 60M内存。因此,会 drop 更多的file cache, swap 更多匿名页。而 Free RAM是强依赖 file cache,file cache 少,FreeRAM 也会少。
内存泄漏
当系统稳定性不好,或者 lmk 杀到 adj 很低时,一般会看下此时内存使用情况(同内存评估的表格),排查内存泄漏问题。这样整体上看,只能定位出哪个模块或者进程存在 memory leak,具体的 leak 点还需要各个 FO 排查。内核很少出现这类问题,一旦怀疑是内核出现 leak,就会采取很脏的方式,标记每个分配路径与回收路径,最终统计定位出问题的点,一般是 patch 形式 debug。
但是用户层的 memory leak 却很常见,一般泄漏的是 malloc 或者 new 后没有对应的 free 掉,导致Active(anon)+ Inactive(anon) + ZRAM 很大。
例:
从下面 LMK 杀进程时的 mem 情况,看到 Free memory 较小,而 swapfree为 0,swap 分区耗尽。
[82916.745910] c3 sprd thm: @@@D thm sensor id:0, cal_offset:-2, rawdata:0x3b4
[82916.765411] c1 lowmemorykiller: Killing 'd.process.acore' (27571), adj 5,
[82916.765411] c1 to free 11576kB on behalf of 'sdcard' (1590) because
[82916.765411] c1 cache 17680kB is below limit 18432kB for oom_score_adj 3
[82916.765411] c1 Free memory is 4124kB above reserved
[82916.765411] c1 swaptotal is 409596kB, swapfree is 0kB
进程的 RSS 信息和 swap 信息如下,而此时 mediaserver 占用空间特别大,用户空间 RSS 为 50M, 而swap 空间为 379M。基本可以确认是 mediaserver 内存泄漏。
lowmemorykiller: [ pid ] uid tgid total_vm rss swap cpu oom_score_adj name
lowmemorykiller: [ |
130] |
0 |
130 |
175 |
48 |
62 |
3 |
-1000 ueventd |
lowmemorykiller: [ |
155] |
1023 |
155 |
1028 |
51 |
45 |
0 |
-1000 sdcard |
lowmemorykiller: [ |
156] |
1036 |
156 |
1497 |
51 |
112 |
2 |
-1000 logd |
lowmemorykiller: [ |
157] |
0 |
157 |
395 |
44 |
2 |
3 |
-1000 healthd |
lowmemorykiller: [ |
158] |
0 |
158 |
592 |
58 |
50 |
0 |
-1000 lmkd |
lowmemorykiller: [ |
159] |
1000 |
159 |
309 |
58 |
30 |
0 |
-1000 servicemanager |
lowmemorykiller: [ |
160] |
0 |
160 |
1447 |
33 |
144 |
1 |
-1000 vold |
lowmemorykiller: [ |
161] |
0 |
161 |
545 |
48 |
175 |
0 |
-1000 debuggerd |
lowmemorykiller: [ |
162] |
1000 |
162 |
10559 |
207 |
173 |
3 |
-1000 surfaceflinger |
lowmemorykiller: [ |
163] |
0 |
163 |
782 |
10 |
43 |
0 |
-1000 modem_control |
lowmemorykiller: [ |
164] |
1000 |
164 |
1559 |
3 |
57 |
3 |
-1000 modemd |
lowmemorykiller: [ |
165] |
1000 |
165 |
1515 |
43 |
70 |
0 |
-1000 wcnd |
lowmemorykiller: [ |
166] |
1000 |
166 |
88 |
0 |
12 |
3 |
-1000 batterysrv |
lowmemorykiller: [ |
173] |
2000 |
173 |
278 |
21 |
41 |
1 |
-1000 sh |
lowmemorykiller: [ |
174] |
0 |
174 |
1444 |
41 |
66 |
1 |
-1000 collect_apr |
lowmemorykiller: [ |
176] |
0 |
176 |
2681 |
20 |
85 |
3 |
-1000 netd |
lowmemorykiller: [ |
177] |
1019 |
177 |
4535 |
92 |
278 |
1 |
-1000 drmserver |
lowmemorykiller: [ |
178] |
1013 |
178 |
146988 |
7926 |
92832 |
1 |
-1000 mediaserver |
Lmk 水位调整
在低内存手机中,常常会涉及 lmk 水位的调整,因为 android 原生水位相对高,常会出现闪退问题或者 CTS 测试进程被 kill。Lmk 水位调整是一种权衡,不会解决所有问题,只能根据项目的性能&稳定性目标,做出均衡。
根据项目经验,以下是Tuning 的大体策略:
- 基本原则是以 Android 计算的原生值为基准,不建议偏差太多。但是我们实际发现原生计算值比较大,多后台性能不怎么好;而且运行大应用会出现闪退。
- 各 minfree 值档位差,最好依据原生的比例,尽量进行模仿。我们只是定最高水位 adj 15 对应的内存值。
- 当系统 ANR 较多时(注:并不是说 ANR 就一定和 LMK 参数相关;但是调节 LMK 参数可以缓解系统的 ANR 情况)
- 加大 oom_adj >= 9(HIDDEN_APP_MIN_ADJ)对应的 minfree 值
- 但该方法会降低多后台的性能(减少后台 APP 个数)
- 通过系统更为积极的 kill 进程来释放 mem;以降低内存回收所使用的 CPU,以及占用的 IO 带宽;加快进程获取 mem 的速度。
- 如果想提高多后台:
- 同时应注意与 HIDDEN_APP_MIN_ADJ(9)以下的 adj 对应的 minfree
- 调低 oom_adj >= 9(HIDDEN_APP_MIN_ADJ)对应 minfree 的值的值保持一定距离
- 该方法和上述需求 1 是矛盾的。多后台和减少 ANR 往往会不可兼得,请根据实际需求综合考虑。
- 如果要避免前台进程发生闪退:
- 如果该值太低,系统内存极低时可能无法恢复过来,导致定屏黑屏等等。
- 调低 FOREGROUND_APP_ADJ 0 对应的 minfree 值
- 如果觉得两个水位之间,间隔太大,没有积极的杀进程(主要是导致性能问 题),可以重新设定Oom_adj 数组,比如设定为:0,1,3,9,11,15(原生值为 0,1, 2,3,9,15)。
- 调节起点:如果 swapfree 相当小时(小于 5%),但 ANON 页和 Filecache 都很大,而且由于 Filecache 很多,没有杀进程。则至少需要将最高杀 APP 水位调整到此时 Filecache 最大值。通过积极的杀进城来释放 swap 的空间,并减轻内存回收的压力。
下面是实际 tuning 的操作方法:
如果存在页颠簸的话,我们还是希望通过 lmk kill 进程来快速回收内存。而 lmk 参考的阀值是 file-cache,那我们可以大概估算出低于 adj 9 所有进程所需的总体 active file-cache,这值可以做为 adj9 的lmk 阀值。有了这个值,再根据原生水位的比例,就可以得出一组 lmk 水位。
一般将上面得到的水位做为系统最不积极的水位(水位再低点,就会出现页颠簸了),然后在这组水位与原生水位之间再设计出几套水位,再加一组比原生水位高点的水位。分别对设计出来的几套水位,进行性能与稳定性测试,选定表现最均衡的一组。
操作方法:
手机开机5分钟:
1)kill 所有后台进程(am kill-all);
2 ) 回收内存(echo 1 > /proc/sys/vm/drop_caches);
3)2 操作后,马上收集内存信息,/proc/meminfo, dumpsys meminfo -a;
- active file-cache 可以做为 adj 529 的内存阀值 base(minfree 数组的第 5 个值,注意除 4);
- 再根据原生水位的比例,设计一组 lmk 水位;
最后,依据 dumpsys meminfo,查看各组进程组的内存占用情况,进行 minfree微调;