1、 内存泄露即使前行有阻碍,自动化测试工具也要不屈不挠,奋勇向前,在市场的大海里劈波斩浪,直挂云帆,竭力争先。
android系统为每一个运行的程序都指定了一个最大运行内存,超过这个值则会触发oom机制,反应在界面就是闪退、 crash现象,导致oom发生的原因比如内存泄露或者是代码不考虑后果使用大量的资源,都有可能导致oom出现的。oom的临界值可以通过adb shell getprop | findstr “heap”查看到:
2、 android的gc机制
android gc机制沿用了java的gc机制,当需要新内存去分配对象的时候而剩余不够的时候,会触发gc,把无用的对象回收掉,其中一个重要的算法便是分代式算法,这个算法把虚拟机分为年轻代、老年代和持久代,对象先分配到年轻代,然后gc多次后还存活的将会移动到老年代,老年代就不会频繁触发gc机制,一般触发频繁的都是年轻代的对象。
3、 为什么会内存泄露
上面我们知道了gc机制,那么如果gc过后程序还是没有内存,那么会发生oom,导致gc后还是没有足够内存分配新对象的主要原因就是内存泄露了。首先要知道内存泄露也就是gc不掉的根源是生命周期长的对象持有生命周期短的对象,导致无用的对象一直无法回收。以下是几个典型的分类:
1)**静态类相关的泄露:**static对象的生命周期伴随着整个程序的生命周期,所以这块要注意不要把一些对象引用添加到static对象里面去,会造成与之关联的对象无法回收。
2)各种资源的释放:包括cursor的关闭,io流的关闭,bitmap的回收等,进行一些带有缓存的资源一定要关闭或者释放。
3)handler的泄露:调用handler的delay的时候,会被认为对象是有用的,导致无法回收,还有handler开启线程去下载东西没有下载完成的时候,也会因为线程导致无法回收activity;或者使用handlerthread的时候,有延迟的方法,都会导致无法回收。其主要原因在于handler是持有activity的引用,主线程不是自带一个looper然后给handler用,导致有关联关系。
4)各种注册引用方法:比如一个常驻的后台线程处理某些时间,把当前对象注册,因为一直持有对象引用,导致这个activity一直保留,所以不用的时候需要反注册。
5)把对象缓存进容器内却忘记remove掉:有时候为了加快页面响应,结果缓存一些对象到容器内,结果越加越多,然后挂掉。
4、 系统级别的内存管理
1)lmk机制和oom_adj的值
android内核有个专用的驱动low-memory-kill,当系统级别的内存不够的时候会根据oom_adj的值以及内存分配状况去kill掉某个进程,oom_adj可以在proc[pid]oom_adj看到,并且这个值会随着进程的状态改变而改变,比如系统进程一般是-16,越大越容易被干掉。
2)5个进程的优先级
前台进程:当前运行的,基本不死 ;
可见进程:界面可以见到,比如被遮挡 ;
服务进程:进程带后台服务的,比如播放器 ;
后台进程:点击home键,但不退出,就是后台进程了,有比较大几率会被杀;
空进程:退出应用程序,还在后台保留这空进程,为的是加快启动速率,最优先。
5、 内存抖动
内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致oom(主要原因还是有因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统直接就返回oom了)
6、 内存名词vss、rss、pss、uss解释
vss - virtual set size 虚拟耗用内存(包含共享库占用的内存)
rss - resident set size 实际使用物理内存(包含共享库占用的内存)
pss - proportional set size 实际使用的物理内存(比例分配共享库占用的内存)
uss - unique set size 进程独自占用的物理内存(不包含共享库占用的内存)
大小规律:
一般来说内存占用大小有如下规律:vss >= rss >= pss >= uss
7、 内存值获取方法
使用命令 adb shell dumpsys meminfo package_name 获取内存信息,如日历的内存信息如下:
pss total:进程各部分内存的消耗,是所有进程pss相加得到系统占用内存的总和
native heap:native代码分配的内存,虚拟机和android框架分配内存。关于什么是native代码,即非java代码分配的内存
dalvik heap:java对象分配的占据内存
dalvik other:类数据结构和索引占据内存
stack:栈内存
private dirty:它基本上是进程内不能被分页到磁盘的内存,也不和其他进程共享,private dirty内存是最重要的部分,因为只被自己进程使用
private clean:是已经映射持久文件使用的内存页(例如正在被执行的代码),因此一段时间不使用的话就可以置换出去
heap alloc:是dalvik堆和本地堆分配使用的大小,它的值比pss total和private dirty大,因为进程是从zygote中复制分裂出来的,包含了进程共享的分配部分
ashmem:不以dalvik-开头的内存区域,匿名共享内存用来提供共享内存通过分配一个多个进程,android匿名共享内存是基于linux共享内存的,都是在tmpfs文件系统上新建文件,并将其映射到不同的进程空间,从而达到共享内存的目的,只是,android在linux的基础上进行了改造,并借助binder+fd文件描述符实现了共享内存的传递。
other dev:内部driver占用的内存
so mmap:c 库代码占用的内存
jar mmap:java 文件代码占用的内存
apk mmap:apk代码占用的内存
ttf mmap:ttf 文件代码占用的内存
dex mmap:dex 文件代码占用的内存
other mmap:其他文件占用的内存
8、 测试场景选择
内存出现泄漏的前提条件一定是有新的内存分配,所以测试场景会选择有新对象创建的场景,并结合用户的使用场景和频率来确定优先级。测试场景主要有以下三种情况,配合测试次数,然后可以每5次获取一次内存值进行判断,一般测试300次,如果各种内存测试完成并等待5分钟后内存没有释放,则高概率存在内存泄露:
1)新画面打开
由于新的画面打开,就会创建新的activity和view,并有许多其他对象被创建。
测试方法:
反复进入退出需要测试的目标activity,如果发现activities和views的一直在增长,则内存泄露一定发生(退出时如果手动gc,则activities和views的数量应该为0)
2)画面旋转
当屏幕旋转时,orientation设置发生了改变,当前显示的activity会被重新创建。
测试方法:进入需要测试的目标activity,反复横竖屏切换,如果发现activities数量等其他值一直在增长,则内存泄露一定发生
3)滑动屏幕
滑动屏幕会使屏幕中显示的对象(比如浏览器小说阅读内容)创建。
测试方法:进入需要测试的目标activity,一直固定某个方向滑动(向左),如果发现内存值一直在增长,则内存泄露一定发生
case例子,仅供参考:
测试过程中的值记录模板,仅供参考:
注意:
1)每个应用的脚本需要获取的信息可以直接涉及好关联应用或进程的数据值,例如测试camera时后台camera服务进程,多媒体进程、相册进程。
2)针对内存泄露的测试,需要开发自动化脚本测试,然后测试过程中获取测试的值存入execl的固定模板,测试完成后根据测试结果数据判断是否有内存泄露
9、 定位内存泄露的原因(如果是真机测试,安装一个debug版本的apk,否则monitor无法显示进程)
方法一:使用ddms(monitor)检测内存泄露--需要
步骤2、然后在打开ddms, 选择heap标签,然后点击cause gc按钮,点击cause gc是手动触发java垃圾回收器,如下图:
如果我们要测试某个activity是否发生内存泄露,我们可以反复进入和退出这个activity, 再手动触发几次垃圾回收,观察上图中 data object这一栏中的 total size的大小是保持稳定还是有明显的变大趋势,如果有明显的变大趋势就说明这个activity存在内存泄露的问题,需要在具体分析。