文档章节

Android内存分析和调优(上)

SuShine
 SuShine
发布于 2015/06/24 13:54
字数 2034
阅读 26
收藏 0
最近我们的android app占用了大量内存,于是领导安排做减少内存占用的工作。
要优化内存,首先要做的就是分析内存占用情况。android提供了多个工具和命令进行内存分析。
 

第一层 Procrank

 
很粗略的,可以使用"adb shell procrank",结果类似于

PID    Vss        Rss        Pss       Uss      cmdline

......
2319 42068K 42032K 13536K 7028K com.xxx
......

该命令可以列出当前系统所有进程的内存占用情况。
PID是进程ID。
Vss是占用的虚拟内存,如果没有映射实际的内存也算进来。
Rss是占用的物理内存。是共享内存+私有内存。因为共享内存是多个进程共用的,所以存在重复计算。
Pss是占用的私有内存加上平分的共享内存。例如一块1M的共享内存被两个进程共享,那每个进程分500K。各进程的Pss相加基本等于实际被使用的物理内存,所以这个经常是最重要的参数。
Uss是私有内存。
cmdline可以看做是apk包名。

通过procrank,只能很宏观的横向比较不同的应用。如果要更细致的了解具体内存是如何使用,则需要进入

第二层 dumpsys meminfo

命令“adb shell dumpsys meminfo package.name”。在4.0 ICS(或者3.0 HoneyComb)之后的系统上,会看到类似下面的输出

                                 Shared   Private  Heap    Heap     Heap
                      Pss      Dirty      Dirty     Size     Alloc      Free
                      ------   ------    ------     ------  ------     ------
Native            16        8           16        3416   3300     79
Dalvik            3884    10592   3580    9560   9022     538
Cursor            0          0           0 
Ashmem        0           0           0 
Other dev       5110    10244   0 
.so mmap       640     1948      396 
.jar mmap      0          0           0 
.apk mmap     68        0           0 
.ttf mmap       817      0           0 
.dex mmap     411      0           0 
Other mmap   55        16         32 
Unknown        2404     660       2388 
TOTAL            13405  23468   6412    12976 12322 617

(如果使用2.3或之前的版本,结果会粗糙一些,很多都被归入了Other,但基本结构是一样的)

stacktrace上有个经常被搜到的帖子对这个格式有说明,虽然针对的是android 2.3格式,但读后非常有收获。
但仍有很多疑问没有解答,例如针对上面的例子,为什么Native heap size那么大,但Pss却那么小?占用内存比较多的Other dev是什么?Unknown又有哪些?等等。
要理解这些,需要知道这个report是如何生成的。实际上,生成report的代码是android的android_os_Debug.cpp
从中我们可以发现,上面列表的数据是由三种方式获取的:
1. Pss/Shared Dirty/Private Dirty三列是读取了/proc/process-id/smaps文件获取的。它会对每个虚拟内存块进行解析,然后生成数据。
2. Native Heap Size/Alloc/Free三列是使用C函数mallinfo得到的。
3. Dalvik Heap Size/Alloc/Free并非该cpp文件产生,而是android的Debug类生成。

后面两个Heap的获取比较简单,我唯一的疑惑是为什么有free的?我的理解是无论是c的malloc还是java的new,最后都是通过mmap系统调用进行内存分配的。而mmap必须以页的4K为单位。所以如果一次一次只需要malloc 2K,则剩下的2K是free的。如果下次再malloc 2K,可以仍然使用上次mmap剩余的2K内存。

至于smaps文件,我们可以通过adb shell cat /proc/process-id/smaps来查看(需要root)。这是个普通的linux文件,描述了进程的虚拟内存区域(vm area)的具体信息。每次mmap一般都会生成一个vm area。
在Android上,一个更加方便的命令是adb shell showmap -a process-id。

第三层 adb shell showmap

该命令也是读取smaps文件,但结果细化的具体的vm area。
该命令输出的每行表示一个vm area,列出了该vm area的start addr, end addr, Vss, Rss, Pss, shared clean, shared dirty, private clean, private dirty,object。 
第二层的dumpsys meminfo其实就是读取这些数据,然后分类(native, dalvik, .so map, etc.)统计生成。
start addr和end addr表示进程空间的起止虚拟地址。
Vss,Rss,Pss跟前面说的一样。
Object可以看做mmap的文件名。

Shared clean,按字面意思,表示共享的干净的数据。共享表示多个进程的虚拟地址可以都指向这块物理空间,表示多个进程共享的so库。为什么这里说是多个进程共享的so而不是所有的so呢?
关于so库的加载,我一直觉得是mmap带MAP_SHARED参数,但看了memory_faq,才知道是MAP_PRIVATE。如果使用showmap命令查看vm area,会发现有的so的内存都属于Shared clean,而有的so则属于private clean。前者一般是当前进程特有的so,而后者一般是通用的so。后来看了对mmap的各种参数的实验(很赞实践精神),才知道第一次以MAP_PRIVATE mmap so,内存都是private clean的。如果另外一个进程mmap了同一个so,那该vm area就变成shared clean了。

Private clean,包括该进程私有的干净的内存。包括前面说的该进程独自使用的so和进程的二进制代码段。
Clean内存的好处是在内存紧张时,可以释放物理内存。因为是clean的,所以不需要写回到disk,只需要下次读取该内存(导致缺页错误)时再从disk读入。

Private dirty,表示该进程私有的不跟disk数据一致的内存段。例如堆(heap),栈(stack),bss段。关于bss段,因为在elf文件为了节约控件没有赋值,所以在加载到内存时赋值为0,于是跟disk就不一致了。在showmap结果中,会发现几乎每个so都有一个显示位[bss]的private dirty段。数据段我估计是private clean的,因为elf文件是有初值的。

Shared dirty开始我一直搞不清楚。后来看了Dalvik vm internal这个video(slides),才明白了些。对于普通的linux进程,当父进程fork子进程时,父进程的虚拟内存区域都会”复制“一份到子进程中。这里”复制“加引号,是因为为了节省内存,也为了减少内存拷贝的时间,使用的是copy-on-write的方法。当子进程对private dirty的堆,栈,bss没有修改时,则是父子进程share这份dirty(因为跟disk没法映射)数据。如果发生改变,则会修改为private dirty。所以android有zygote进程,是所有android apps进程的父进程,在其中会加载resource等资源(下文会看到,最简单的应该也有大概5M resource,例如图片),这些资源都是只读的。具体的apps继承了这些shared dirty的数据,因为不修改它们,所以也不用分配多余的内存空间。

由于android使用的linux没有swap分区,所以dirty的数据必须常驻内存。所以dumpsys meminfo会把private dirty和shared dirty重点列出来,这也是我们优化内存的重点。

现在可以回答一个前面提到的问题,为什么Native Heap(根据mallinfo系统调用得到)很大而Native Pss(根据swaps得到)很小。我觉得这是dumpsys meminfo的一个bug。根据android_os_Debug.cpp的代码,object名字是[heap]的段被认为是native heap。这在2.3是正确的,但在4.0之后,[heap]为名字的段却很小(只有几K)。同时,我却发现有大量的[anon]的区域。我认为anon是anonymous的缩写。malloc一般是通过mmap来分配内存的,而参数是MAP_ANONYMOUS。所以我觉得这些[anon]是native heap。从大小上看,现在这些[anon]被看做是Unkown的一部分,也跟hative heap的大小差不多。

在dumpsys meminfo结果的其他值比较大的行,.so表示映射的so库(vm area行的object名称包含.so字样),.dex表示映射的.dex文件(dalvik的虚拟机二进制码),Other dev表示映射其他的/dev的(dalvik的heap也是映射到特殊的/dev上)。加上native和dalvik的heap,下次写如何具体分析这五项。

本文转载自:http://blog.csdn.net/sfshine/article/details/24288973

共有 人打赏支持
SuShine
粉丝 123
博文 493
码字总数 141946
作品 0
朝阳
后端工程师
流言终结者- Flutter和RN谁才是更好的跨端开发方案?

背景 论坛上很多小伙伴关心为什么闲鱼选择了Flutter而不选择其他跨端方案?站在质量的角度,高性能是一个很重的因素,我们使用Flutter重写了宝贝详情页之后,对比了Flutter和Native详情页的性...

闲鱼技术
09/10
0
0
一步步拆解 LeakCanary

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 java 源码系列 - 带你读懂 Reference 和 ReferenceQueue https://blog.csdn.net/gdutxiaoxu/article/details/80738581 一步步拆解 ...

xujun9411
07/04
0
0
Android性能优化:这是一份详细的布局优化 指南(含、、)

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的布局优化,希望你们会喜欢。 目录 /** 实例说明:在上述例子,在布局B中 通过标签引用布局C 此时:布局层级为 = RelativeLa...

Carson_Ho
05/14
0
0
Service的生命周期与Activity生命周期区别

碰到一面试题 简述activity/service生命周期? 组件的生命周期 应用程序组件都有一个生命周期,从响应Intent的Android实例开始到这个实例被销毁。在这期间,他们或许有效或许无效,有效时或许...

xiahuawuyu
2012/07/24
0
0
Android性能优化:那些不可忽略的绘制优化

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的绘制优化,希望你们会喜欢。 目录 // 方式2:在 BaseActivity 的 onCreate() 方法中使用下面的代码移除 优化方案2:移除 控件...

Carson_Ho
05/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

RxJS的另外四种实现方式(四)——性能最高的库(续)

接上一篇RxJS的另外四种实现方式(三)——性能最高的库 上一篇文章我展示了这个最高性能库的实现方法。下面我介绍一下这个性能提升的秘密。 首先,为了弄清楚Most库究竟为何如此快,我必须借...

一个灰
54分钟前
1
0
麒麟AI首席科学家现世

8月31日,华为发布了新一代顶级人工智能手机芯片麒麟980,成为全球首款7nm工艺手机芯片,AI方面也实现飞跃,支持人脸识别、物体识别、物体检测、图像分割、智能翻译等。 虽然如今人人都在热议...

问题终结者
昨天
1
0
告警系统主脚本、告警系统配置文件、告警系统监控项目

告警系统主脚本 main.sh内容 #!/bin/bash#Written by aming.# 是否发送邮件的开关export send=1# 过滤ip地址export addr=`/sbin/ifconfig |grep -A1 "ens33: "|awk '/inet/ {pr...

芬野de博客
昨天
2
0
MySQL autocommit探究

-- sessionA:tx_isolation=REPEATABLE-READmysql> select connection_id();+-----------------+| connection_id() |+-----------------+| 28 |+-----------------+......

安小乐
昨天
8
0
c++多线程锁 Mutex  自动判断死锁

c++多线程锁可以使用absl::Mutex std::mutex这两种,下面是demo代码。 使用absl:Mutex的时候打印: [mutex.cc : 1338] RAW: Cycle: [mutex.cc : 1352] RAW: mutex@0x683b68 stack: @ 0x43856......

青黑
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部