内存监控
单独分析某一时刻的内存情况可以排查潜在的内存不合理使用,除此之外,我们也需要关注App运行期间内存的整体变化,比如在自动化测试期间定期获取内存数据,通过整合一段时间内的内存数据,形成一个曲线报表;再比如将每个版本的的内存曲线作对比,可以直观的了解某一个版本是否出现了内存使用激增。
procstats获取进程内存信息
如何获取内存的快照呢?
根据Android SDK的提供的工具,我们一般可以用adb配套的命令工具来获取,比如说获取一段时间内整个内存的使用报告,可以用procstats
:
aven-mac-pro-2:platform-tools aven$ adb shell dumpsys procstats --hours 1
那么procstats支持什么参数?--hours 1
肯定代表的是一个小时的范围。其实这些参数很多,不需要记忆,使用的使用直接查看帮助文档就好了。
一般命令行的帮助命令都是-h
或者--help
之类的:
aven-mac-pro-2:platform-tools aven$ adb shell dumpsys procstats -h
Process stats (procstats) dump options:
[--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]
[--details] [--full-details] [--current] [--hours N] [--last N]
[--max N] --active] [--commit] [--reset] [--clear] [--write] [-h]
[--start-testing] [--stop-testing]
[--pretend-screen-on] [--pretend-screen-off] [--stop-pretend-screen]
[<package.name>]
--checkin: perform a checkin: print and delete old committed states.
-c: print only state in checkin format.
--csv: output data suitable for putting in a spreadsheet.
--csv-screen: on, off.
--csv-mem: norm, mod, low, crit.
--csv-proc: pers, top, fore, vis, precept, backup,
service, home, prev, cached
--details: dump per-package details, not just summary.
--full-details: dump all timing and active state details.
--current: only dump current state.
--hours: aggregate over about N last hours.
--last: only show the last committed stats at index N (starting at 1).
--max: for -a, max num of historical batches to print.
--active: only show currently active processes/services.
--commit: commit current stats to disk and reset to start new stats.
--reset: reset current stats, without committing.
--clear: clear all stats; does both --reset and deletes old stats.
--write: write current in-memory stats to disk.
--read: replace current stats with last-written stats.
--start-testing: clear all stats and starting high frequency pss sampling.
--stop-testing: stop high frequency pss sampling.
--pretend-screen-on: pretend screen is on.
--pretend-screen-off: pretend screen is off.
--stop-pretend-screen: forget "pretend screen" and use the real state.
-a: print everything.
-h: print this help text.
<package.name>: optional name of package to filter output by.
``
默认procstats打印出所所有进程的信息,对我们来说,可能有点多,毕竟我们一般就是想看看待测应用的数据,这个时候可以跟上应用的包名,再加上`-a`打出所有信息:
```shell
aven-mac-pro-2:platform-tools aven$ adb shell dumpsys procstats --hours 3 -a cn.hacktons.leakdemo
AGGREGATED OVER LAST 3 HOURS:
Per-Package Stats:
* cn.hacktons.leakdemo / u0a86 / v1:
Process cn.hacktons.leakdemo (unique, 1 entries):
SOn /Norm/Top : +3h33m17s952ms
TOTAL : +3h33m17s952ms
PSS/USS (1 entries):
SOn /Norm/Top : 49287 samples 13MB 29MB 42MB / 9.1MB 23MB 38MB
myID=257eab6 mCommonProcess=257eab6 mPackage=cn.hacktons.leakdemo
Total procs: 0 shown of 26 total
Summary:
* cn.hacktons.leakdemo / u0a86 / v1:
TOTAL: 81% (13MB-29MB-42MB/9.1MB-23MB-38MB over 49287)
Top: 81% (13MB-29MB-42MB/9.1MB-23MB-38MB over 49287)
Run time Stats:
SOn /Norm: +4h22m13s88ms
TOTAL: +4h22m13s88ms
Memory usage:
Persist: 157MB (54 samples)
Top : 38MB (49328 samples)
ImpFg : 91MB (91 samples)
ImpBg : 89MB (128 samples)
Service: 23MB (82 samples)
Receivr: 6.8KB (51 samples)
LastAct: 12MB (46 samples)
CchAct : 63MB (9 samples)
CchEmty: 75MB (118 samples)
TOTAL : 547MB
ServRst: 70 (13 samples)
Start time: 2018-03-26 07:13:00
Total elapsed time: +4h22m13s92ms (partial) (swapped-out-pss) libart.so
Internal state:
mRunning=false
Available pages by page size:
Zone 0 Unmovable 2 1 2 0 0 0 0 0 0 0 0
Zone 0 Reclaimable 3 0 4 7 6 5 4 1 0 0 0
Zone 0 Movable 1 0 1 0 1 1 1 0 0 0 0
Zone 0 Reserve 0 0 0 0 0 0 0 0 0 0 0
Zone 0 CMA 0 0 0 0 0 0 0 0 0 0 0
Zone 0 Isolate 0 0 0 0 0 0 0 0 0 0 0
Zone 0 Unmovable 142 352 261 51 12 0 0 0 0 0 0
Zone 0 Reclaimable 18 18 24 17 0 0 0 0 0 0 0
Zone 0 Movable 100 2635 2270 854 295 55 5 0 0 0 0
Zone 0 Reserve 0 0 0 0 0 0 0 0 0 0 1
Zone 0 CMA 839 838 837 625 682 412 189 41 5 2 5
Zone 0 Isolate 0 0 0 0 0 0 0 0 0 0 0
Zone 0 Unmovable 39 50 17 5 5 2 1 2 0 2 1
Zone 0 Reclaimable 0 0 0 0 0 0 0 0 0 0 0
Zone 0 Movable 55 119 896 394 139 60 45 26 10 0 1
Zone 0 Reserve 0 0 0 0 0 0 0 0 0 0 1
Zone 0 CMA 0 0 0 0 0 0 0 0 0 0 0
Zone 0 Isolate 0 0 0 0 0 0 0 0 0 0 0
meminfo获取内存快照
除了获取一个时间段内的进程的粗略信息,我们也可以通过meminfo
来获取指定进程的内存快照。
aven-mac-pro-2:Desktop aven$ adb shell dumpsys meminfo cn.hacktons.leakdemo
Applications Memory Usage (in Kilobytes):
Uptime: 88948299 Realtime: 88948299
** MEMINFO in pid 12049 [cn.hacktons.leakdemo] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 4102 3840 0 0 13824 12187 1636
Dalvik Heap 550 516 0 0 9355 4678 4677
Dalvik Other 392 392 0 0
Stack 228 228 0 0
Ashmem 5 0 0 0
Other dev 10 0 8 0
.so mmap 1423 196 0 0
.apk mmap 396 0 0 0
.ttf mmap 45 0 0 0
.dex mmap 3709 4 2316 0
.oat mmap 285 0 0 0
.art mmap 3914 3668 0 0
Other mmap 13 4 0 0
Unknown 355 328 0 0
TOTAL 15427 9176 2324 0 23179 16865 6313
App Summary
Pss(KB)
------
Java Heap: 4184
Native Heap: 3840
Code: 2516
Stack: 228
Graphics: 0
Private Other: 732
System: 3927
TOTAL: 15427 TOTAL SWAP PSS: 0
Objects
Views: 17 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 2 AssetManagers: 3
Local Binders: 9 Proxy Binders: 16
Parcel memory: 3 Parcel count: 12
Death Recipients: 0 OpenSSL Sockets: 0
WebViews: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
上面的输出更加细致,区分了App中的Native Heap,Dalvik Heap等。
定时采集内存数据就可以利用meminfo来处理。他所支持的参数也可以通过-h输出:
aven-mac-pro-2:Desktop aven$ adb shell dumpsys meminfo -h
meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
-a: include all available information for each process.
-d: include dalvik details.
-c: dump in a compact machine-parseable representation.
-s: dump only summary of application memory usage.
-S: dump also SwapPss.
--oom: only show processes organized by oom adj.
--local: only collect details locally, don't call process.
--package: interpret process arg as package, dumping all
processes that have loaded that package.
--checkin: dump data for a checkin
If [process] is specified it can be the name or
pid of a specific process to dump.
数据的理解
现在我们已经知道如何获取大量的数据,如何读懂这些数据也是个问题。我们可以通过对比系统设置中的可视化页面来推测含义,也可以分析源代码,注释来理解不同输出的含义。
相关源代码:com.android.internal.app.ProcessStats.java
这些工作读者可以可好自己去完成。下面我们简单看下这段常见输出的含义:
TOTAL: 81% (13MB-29MB-42MB/9.1MB-23MB-38MB over 49287)
As can be seen in the example below, the output displays what percentage of time the application was running, and the PSS and USS as minPSS-avgPSS-maxPSS/minUSS-avgUSS-maxUSS over the number of samples.
这一行日志的含义大致是:在我们观察的指定时间段内,该进程运行时间占了81%,数据采采样总计49287次,PSS和USS的情况是13MB-29MB-42MB/9.1MB-23MB-38MB
关于PSS和USS的概念,可以简单了解下,一般在linux中经常见到,特别是输出系统信息的时候。
- PSS: proportional set size的缩写,代表一个进程所使用的内存总和,包括独占的私有内存和进程间共享的内存
- USS: unique set size的缩写,代表一个进程所使用的独占内存,和上面的PSS的差异就在于不包含共享内存
举个例子来说:
某一时刻,进程cn.hacktons.leakdemo的非共享内存大小为23MB,进程cn.android.system的非共享内存为50MB, cn.hacktons.leakdemo和cn.android.system共享内存大小是12MB,那我们可以简单得出以下PSS数据:
- cn.hacktons.leakdemo PSS = 23MB + 12/2MB = 29MB
- cn.android.system PSS = 50MB + 12/2MB = 56MB
当然这里对共享内存简单二等分的做法也并不是锁一定共享使用的只有1/2,这是计算使用量时的一个简单处理方法而已。
监控报表
通过脚本或者开发程序定时获取内存信息,我们可以得到一组内存数据,利用可视化的图表,比如曲线图将采样的数据实时或者非实时展现出来,就实现了我们需要的监控图表;