事情的终局强如事情的起头;存心忍耐的,胜过居心骄傲的。—传道书7:8
RAM(random-access memory)即内存的使用情况对系统的性能影响很大,OOM问题、内存泄露、程序卡顿等诸多问题,都跟不合理的内存使用相关,并且这类问题一般都比较隐晦,要解决该类问题,熟悉内存查看的方法很有必要。
本篇博文介绍Android平台上常用的内存观测方法。
procrank与procmem
这两个命令均需要root权限才能运行。
procrank
procrank可以快速的总览当前系统各个进程的内存使用情况,其主要展示了4个指标:
- Vss(virtual set size): 进程可访问的内存大小,包含了共享内存。Android系统中大量的内存空间会被多个进程共享,比如一个进程在运行期间会调用到其他进程,那么Vss也就将调用到的其它进程的内存统计进来了。
- Rss(resident set size): 进程实际使用的内存大小,同样也包含了共享内存,但它只将实际用到的共享内存统计入内。
- Pss(proportional set size): 进程自身内存大小+按比例分配的共享内存大小。
- Uss(unique set size): 进程自身的内存大小,不包括使用到的其他共享内存。程序退出后,系统就会多出Uss大小的内存出来。
举个例子说明下:假如有进程A,其自身运行期间的私有内存大小为a,进程B运行期间占据了b大小的内存。
A运行期间需要依赖B,则Vss=a+b。
A依赖B,但只是用到了B进程下的部分内存区域,大小为b1。则Rss=a+b1(b1<=b)
除了A以外,还有3个进程也同样在使用B,则Pss=a+b/(1+3)
只统计A进程自身占用的内存,则Uss=a
procrank的使用说明
# procrank -h
Usage: procrank [ -W ] [ -v | -r | -p | -u | -s | -h ]
-v Sort by VSS.
-r Sort by RSS.
-p Sort by PSS.
-u Sort by USS.
-s Sort by swap.
(Default sort order is PSS.)
-R Reverse sort order (default is descending).
-c Only show cached (storage backed) pages
-C Only show non-cached (ram/swap backed) pages
-k Only show pages collapsed by KSM
-w Display statistics for working set only.
-W Reset working set of all processes.
-h Display this help screen.
其运行结果如下:
PID Vss Rss Pss Uss cmdline
3062 1735540K 131548K 85448K 78648K system_server
3428 1028392K 115896K 74462K 68900K com.android.systemui
26426 1012992K 83272K 42890K 39664K com.android.settings
3806 1001248K 81064K 41639K 38840K com.android.phone
353 152208K 40524K 28086K 26516K /system/bin/cameraserver
3414 978800K 64344K 27342K 25120K com.google.android.inputmethod.pinyin
9885 1643528K 64880K 24821K 22012K com.android.mms
分析内存问题主要关注的是Uss跟Pss,尤其是Uss.因为Vss和Rss包含了共享内存,单个进程分析内存大小时,会有干扰。
由于procrank会列出整个系统进程的内存信息,而更多时候我们往往只关心特定进程的内存信息。可以用下面的shell语句过滤出我们关心的内容。
while true;do adb shell procrank|grep <proc-keywords>; sleep 6;done
上述命令会每隔6s打印出关心的进程内存信息。
procmem
procrank命令从宏观上给出了进程的内存总体情况,但如果需要详细分析某个进程的内存分配细节,这个时候就需要procmem出场了。
procmem使用说明:
# procmem
Usage: procmem [ -w | -W ] [ -p | -m ] [ -h ] pid
-w Displays statistics for the working set only.
-W Resets the working set of the process.
-p Sort by PSS.
-m Sort by mapping order (as read from /proc).
-h Hide maps with no RSS.
比如我们查看settings应用的内存分布:
# procmem -m 26426 (26426为settings的pid)
Vss Rss Pss Uss ShCl ShDi PrCl PrDi Name
------- ------- ------- ------- ------- ------- ------- -------
2084K 0K 0K 0K 0K 0K 0K 0K /dev/ashmem/dalvik-main space (deleted)
194524K 0K 0K 0K 0K 0K 0K 0K /dev/ashmem/dalvik-main space (deleted)
12560K 10492K 10492K 10492K 0K 0K 10488K 4K /dev/ashmem/dalvik-main space 1 (deleted)
1780K 0K 0K 0K 0K 0K 0K 0K /dev/ashmem/dalvik-main space 1 (deleted)
182268K 0K 0K 0K 0K 0K 0K 0K /dev/ashmem/dalvik-main space 1 (deleted)
7436K 7436K 4277K 4172K 3264K 0K 4172K 0K /data/dalvik-cache/arm/system@framework@boot.art
31116K 15260K 1696K 124K 15136K 0K 124K 0K /data/dalvik-cache/arm/system@framework@boot.oat
4K 4K 0K 0K 4K 0K 0K 0K /data/dalvik-cache/arm/system@framework@boot.oat
0K 0K 0K 0K 0K 0K 0K 0K [vectors]
------- ------- ------- ------- ------- ------- ------- -------
1130688K 148940K 108346K 104332K 44512K 96K 62100K 42452K TOTAL
中间大量的输出结果省略。
从procmem输出的结果可以看到它给出了内存是如何分配的细节,但面对这茫茫多的输出数据,我们该如何从中寻找问题点呢?
内存异常问题往往伴随着内存泄露,而内存泄露的本质就是申请的内存区域得不到正常释放,表现在procmem的输出结果就是多条同名的记录出现。因此如果发现procmem输出的结果里某条记录出现次数过多,比如五六百次,那么我们应当小心对待了。
dumpsys meminfo
dumpsys meminfo
也是分析内存的一把利器,如果不跟进程名或者进程号,则输出系统整体的内存情况,跟进程名则输出该进程的内存分配情况。
# adb shell dumpsys meminfo 26426
Applications Memory Usage (in Kilobytes):
Uptime: 206814437 Realtime: 374715375
** MEMINFO in pid 26426 [com.android.settings] **
Pss Private Private Swap Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 32062 32016 0 0 67584 27961 39622
Dalvik Heap 9623 9504 0 0 21384 9096 12288
Dalvik Other 4857 4824 0 0
Stack 404 404 0 0
Ashmem 2 0 0 0
Gfx dev 40220 40220 0 0
Other dev 6 0 4 0
.so mmap 1258 184 16 0
.jar mmap 8 8 0 0
.apk mmap 865 0 392 0
.ttf mmap 63 0 8 0
.dex mmap 6464 8 6456 0
.oat mmap 1694 0 124 0
.art mmap 4280 4172 0 0
Other mmap 563 4 156 0
EGL mtrack 13056 13056 0 0
Unknown 601 600 0 0
TOTAL 116026 105000 7156 0 88968 37057 51910
App Summary
Pss(KB)
------
Java Heap: 13676
Native Heap: 32016
Code: 7196
Stack: 404
Graphics: 53276
Private Other: 5588
System: 3870
TOTAL: 116026 TOTAL SWAP (KB): 0
Objects
Views: 985 ViewRootImpl: 3
AppContexts: 11 Activities: 7
Assets: 5 AssetManagers: 4
Local Binders: 83 Proxy Binders: 40
Parcel memory: 389 Parcel count: 26
Death Recipients: 0 OpenSSL Sockets: 0
WebViews: 0
SQL
MEMORY_USED: 339
PAGECACHE_OVERFLOW: 178 MALLOC_SIZE: 117
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 228 95 469/191/4 /data/user_de/0/com.android.settings/databases/search_index.db
横轴的几个重要参数:
- Pss Total:按比例分配占用内存 (PSS) 总量,与procrank里的Pss含义一样。
- Private Dirty: 进程私有的内存分配量,当进程退出,将有 Private Dirty大小的内存被系统回收。Private Dirty是已被修改而必须保持在 RAM 中的 RAM 页,相对应的Private Clean指RAM已从某个持久性文件(例如正在执行的代码)映射的 RAM 页,如果一段时间不用,可以移出分页。
- Heap Size:进程当前可访问到的堆内存大小,包含了共享的其他进程内存。
- Heap Alloc:进程当前已经占有的堆内存大小。
纵轴的几个重要参数:
- Unknown:系统无法将其分类到其他更具体的一个项中的内存,当Unknown指持续增大,且不会明显回落,很可能是native的内存泄漏了。
- AppContexts & Activities:进程中当前活动的应用 Context 和 Activity 对象数量,它们不断增长则表示有内存泄漏。
proc节点下隐藏的内存信息
/proc/[pid]节点下的内容包含了该进程更为详细的信息。不过查看它需要root权限。事实上不光内存信息,有关该进程的各种信息基本都能在该节点下找到。
# cd /proc/26426
# ls
attr clear_refs coredump_filter exe limits maps mounts ns oom_score_adj reclaim sessionid stat syscall
auxv cmdline cwd fd loginuid mem mountstats oom_adj pagemap root smaps statm task
cgroup comm environ fdinfo make-it-fail mountinfo net oom_score personality schedstat stack status wchan
以上展现了进程26426的相关信息,它们都被记录在对应的文件节点上。分析内存问题主要看maps节点,它会记录26426进程的详细内存映射关系
# cat maps |head
12c00000-12e09000 ---p 00000000 00:04 12528 /dev/ashmem/dalvik-main space (deleted)
12e09000-1ec00000 ---p 00209000 00:04 12528 /dev/ashmem/dalvik-main space (deleted)
32c00000-33844000 rw-p 00000000 00:04 12529 /dev/ashmem/dalvik-main space 1 (deleted)
33844000-33a01000 ---p 00c44000 00:04 12529 /dev/ashmem/dalvik-main space 1 (deleted)
33a01000-3ec00000 rw-p 00e01000 00:04 12529 /dev/ashmem/dalvik-main space 1 (deleted)
70e4b000-7158e000 rw-p 00000000 103:0c 497764 /data/dalvik-cache/arm/system@framework@boot.art
7158e000-733f1000 r--p 00000000 103:0c 497763 /data/dalvik-cache/arm/system@framework@boot.oat
733f1000-733f2000 r-xp 01e63000 103:0c 497763 /data/dalvik-cache/arm/system@framework@boot.oat
733f2000-733f3000 r--p 01e64000 103:0c 497763 /data/dalvik-cache/arm/system@framework@boot.oat
733f3000-733f4000 rw-p 01e65000 103:0c 497763 /data/dalvik-cache/arm/system@framework@boot.oat
procmem获取到的信息就跟maps节点记录的一样,maps更加详细的标示出了内存分配的起至位置。
参考链接
http://stevevallay.github.io/blog/2014/11/17/memory-leak/
https://androidzhibinw.github.io/android/app/startup/activity/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/%E5%90%AF%E5%8A%A8/%E5%88%86%E6%9E%90/2015/09/21/android-app-startup-process/
http://shooting.logdown.com/posts/318965-android-memory-allocation
https://developer.android.com/studio/profile/investigate-ram.html?hl=zh-cn