Android系统内存泄露之必备调试工具

来自:麻辣软硬件(微信号:VOSDeveloper),作者:达不优艾罗


作为Android系统工程师,常年在不同产品项目上会遇到各种内存泄漏,可能是某个应用,可能是framework层的定制化模块,又或者是某个native的进程。那么遇到这样的问题我们有哪些工具可以使用呢?

相关基础知识

后面的章节将涉及到具体的工具,在此之前我们先看看几个基础的概念:

  • VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

  • RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

  • PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

  • USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS,如果是看某个进程的独占内存,一般用USS,若要考虑到共享库内存的情况,则可以参考PSS,接下来我们看看具体的工具。

具体的调试工具

procrank

procrank命令可以用来查看当前系统各进程内存(VSS/RSS/PSS/USS)使用快照。

执行命令“procrank”的部分信息如下:

procmem

procmem可以用来查看系统某一个进程的内存(VSS/RSS/PSS/USS)使用情况。可以用ps或者上文介绍的procrank查看进程pid

执行命令“procmem pid”的部分信息如下:

其中:

  • ShCl(shared clean): 干净的共享数据, 也就是说, 当前有多个进程的虚拟地址指向该物理空间, 且当前进程空间该数据与disk上一致。

  • ShDi(shared dirty) 脏共享数据, 与上面对应, 该进程空间的共享数据与disk上数据不一致。

  • PrCl(private clean) 进程私有干净数据, 与disk数据一致。

  • PrDi(private dirty) 进程私有脏数据, 与disk数据不一致。

librank

librank可以与procrank互补使用,是procmem的扩展,它可以显示某个库文件或者共享文件真实的内存大小。librank数据来源与procmem一样,但是procmem针对的是单个进程的内存占用情况。 而librank是显示某个库或者文件被哪些进程共享, 并显示占用大小,执行命令“librank”的部分信息如下:

dumpsys meminfo

关于dumpsys命令大家可以查看我们之前的一篇文章:Android系统Debug神器之dumpsys

dumpsys meminfo可以查看各个进程的PSS占用情况,执行命令“dumpsys meminfo com.gitvdemo.video”的部分信息如下:

cat /proc/meminfo

这条命令主要是帮助我们查看系统内存的详细信息,执行命令后有一些关键字在这里解释一下:

  • MemTotal:  系统总的可以用物理内存。

  • MemFree:  系统中空闲的物理内存。

  • MemAvailable:  系统可用内存= MemFree + Cached + Buffers。

  • Buffers:  用于文件缓冲的内存大小。

  • Cached:  用于高速缓存器占用的内存大小。

  • SwapCached:  用于高速缓存器占用的swap大小。

  • Active:  活跃使用中的内存, 一般不会回收。

  • Inactive:  最近未被使用的内存, 可以被回收。

  • SwapTotal:  swap分区大小。

  • AnonPages:  匿名页。

  • Shmem:  共享内存。

  • Slab:  内核 slab分配器。

等等,还有一些关键字就不一一列举了。

vmstat

vmstat是一个非常不错的监控工具,可以实时提供memory、blockIO和CPU状态信息。它用来对系统整体的情况进行统计,通过它可以快速了解当前系统运行情况及可能碰到的问题。

我们执行命令“vmstat 1 5”时会看到如下信息:

首先解释下命令:

  • vmstat后的第一个参数表示每隔1秒重新测量并反馈数据。

  • vmstat后的第二个参数表示反馈5次后关闭程序。

查看内存相关的信息我们主要查看memory和swap即可,下面解释下各个参数的含义:

procs

  r: Running队列中进程数,这个值超过cpu个数就会出现cpu瓶颈。

  b: IO wait 的进程数, 该值过大,可能是等待文件操作的进程多或者文件读写慢。

memory

  swpd:使用的虚拟内存大小. 如果大于0,表示手机物理内存不足. 如果不是应用内存泄漏的原因,就是当前后台执行的activity、jobs过多了。

  free: 系统可用的物理内存,单位KB。

  buff: 系统用作buffers的内存数,用来存储比如目录中内容,权限等的缓存。

  cache: 系统用作cache的内存数,用来缓存打开的文件或者图片等。

swap

  si: 每秒从磁盘(disk)读入的内存大小,如果这个值大于0,表示物理内存不足或者内存泄漏,需要查找耗内存的进程。

  so: 每秒从内存写入磁盘的内存大小,如果这个值大于0,表示物理内存不足或者内存泄漏,需要查找耗内存的进程。

io

  bi: 块设备接收的块数量,单位blocks/s。

  bo: 块设备每秒发送的块数量,单位blocks/s。

system

  in: (interrupts) 在delay的时间内(默认为1秒)系统产生的中断数,包括时间中断。

  cs: (context switch) 在delay的时间内(默认为1秒)系统上下文切换的次数。

cpu

  us: 用户(non-kernelcode)占用的CPU时间比。

  sy: 系统时间(kernel code)占用的CPU时间比。

  id: Idle 闲置CPU时间比 %,一般情况下id + us + sy = 100%,当然也有可能99%或者101%。

  wa: (IO wait) CPU等待IO完成的时间占比。

dumpcache

dumpcache工具是Android7.0之后才有的工具,用于查看文件系统中每个文件被cache到内存的大小,可以用来判断缺页错误时加载的文件。

valgrind

valgrind工具套件包括 Memcheck(用于检测 C 和 C ++ 中与内存相关的错误)、Cachegrind(缓存分析器)、Massif(堆分析器)和其他几种工具。

valgrind对于可退出的进程进行调试比较合适, 可以对应用的native代码进行调试。

在Android系统上使用valgrind需要重新编译:

1mmma -j6 external/valgrind

然后将生成的lib/valgrind目录与bin/valgrind文件 push到对应的系统目录下,即可使用。

比如检测test命令是否存在内存泄露问题:

1valgrind --tool=memcheck --leak-check=full--error-limit=no --show-reachable=yes test

输出结果的关键字段说明:

  • definitely lost:  已明确丢失的内存。

  • indirectly lost:  间接丢失的内存(指向该内存的指针位于内存泄露处)。

  • still reachable:  指针指向的动态内存还没有被释放就退出了。

  • possibly lost:  可能丢失的内存。

  • suppressed:  使用valgrind的某些参数取消了的错误。

注:valgrind有个缺点,它会大大降低程序的运行速度。

其他内存调试手段

  • mprotect(c函数):保护指定的内存空间,遇到踩内存的情况可以crash掉程序,让始作俑者无所遁形。

  • StricMode(java类):严苛模式检测应用内存泄漏。

  • showmap(命令):查看进程虚拟进程空间的内存分配情况和详细的干净脏数据信息。

  • AddressSanitizerCLang

  • Android Profiler(Android Studio 3.0): 基于Android Studio 3.0的一款图形化内存泄漏检测工具。

    ......

写在最后

好了,工具介绍就到这里了,上文提到的这些工具你都用过哪些呢?多用用工具,掌握技巧,想必是可以提高我们debug效率的。工欲善其事必先利其器,工具用得好,很多时候都会事半功倍。

总之系统是复杂的,在不同的场景下灵活使用不同工具(或者工具组合)来定位解决问题是非常有必要的,本文介绍的几款内存调试工具希望对你有些许帮助。

推荐↓↓↓
安卓开发
上一篇:华为新发布的方舟编译器,是真牛批还是吹牛皮? 下一篇:视频播放的软解与硬解功能,你知道多少?