【学习笔记】APP性能---内存测试流程及Dalvik Heap常见问题

参考《移动APP性能评测与优化》,总结内存测试相关内容。

一、测试流程

简单借助测试工具,容易明显的内存问题,之后剩下的是复杂而且不明显的问题,或者有些问题可以归属到优化范畴或者产品策略,不简单是内存问题。

对于较为成熟的软件,随机乱测的测试方法有效性比较低。如果是较深层次问题,不容易发现和找到原因;因此有必要总结一套成熟的流程方法,提高测试的有效性;

常见的测试方法有以下:

(1)Monkey/UIAutomator类常规压力测试;

(2)大数据/操作的峰值压力测试;

(3)长时间运行的稳定性测试;

上面这些方法都可以叠加在内存测试的方案中,观察这类场景下的应用内存情况,经常能够发现内存泄漏或OOM的问题;

参考常见性能测试的方案,以及总结以往内存性能测试的经验,总结出一套内存测试的经验性流程,下面介绍这个流程中的要点:

1、代码

通常用来进行内存测试的版本是纯净版本,不应该附加多余的Log和调试用组件。例如有些情况下,为了测试界面延迟/函数执行时间等性能,会加入一些桩点代码。

在内存测试中这些代码是不必要的,他们可能会分配临时内存,引起更多的GC,导致应用出现运行缓慢、卡顿等现象。

2、测试场景

测试场景通常有两类:

(1)当前有新开发或者改动的某项功能,需要对该功能进行性能测试;---因此测试场景主要针对该功能组织,包括功能的开启前、运行、结束后等测试点。

(2)整体性能,考察应用的常见场景,在综合使用情况下的性能指标;---测试场景应当包括启动后待机、切换到后台、执行主要功能、以及反复执行各功能。

在各类场景中,经常作为测试重点的有:

(1)包含了图片显示的界面;

(2)网络传输大量数据;

(3)需要缓存数据的场景;

3、测试场景转换成用例

选取了测试场景后,用例设计也要考虑内存测试的特点。一些常见的方法是:

(1)结合场景比较操作前后或不同版本的内存变化;

(2)显示多张图片的前后进程;

(3)多个场景来回切换;

(4)长时间运行进程的内存增长;

4、执行

由于GC和广播机制的存在,应用内存通常都在不停地波动,幅度可能会达到几百KB,因此执行时需要考虑这种情况。

在采集数据时,需要多次采集并计算平均值。

执行完成,就可以根据数据进行比较初步的分析以确定方向。

Dalvik Heap部分,即由Java代码直接分配的内存,可以通过IDE直接观察到使用情况,也可以使用MAT进行细致的分析。

二、Dalvik Heap的常见问题

随着测试的执行,随之而来是一大堆产生的数据。对产生的数据进行分析,找出可能存在的问题,以及问题的原因是接下来的重点;

由于大部分Android应用是以Java代码开发的,所以Dalvik Heap内存出现问题也是最常见的情况。常见的现象有以下几种:

1、随着功能的反复执行,Heap内存一直在持续增长;

这种情况通常是出现了内存泄漏,这种情况最适合用LeakCanary等泄露检查工具进行白盒测试分析;

2、代码执行时出现了频繁的GC,Heap Alloc内存大幅度波动;

这种情况通常是分配了许多临时变量或数组,随后又被迅速回收,这种情况在确定具体场景后适合使用Heap Viewer/Allocation Tracker等工具来查看具体分配的对象;

3、每次启动应用后,Heap内存相比以前版本稳定增长;

这种情况通常出现在启动后待机或使用某功能后,可能是由新功能及代码改动引入的固定内存增长。这种情况适合获取Heap Dump后进行多版本或功能使用前后对比,能够迅速找到增长的原因。

4、Heap Alloc变化不大,但进程的Dalvik Heap Pass(Proportional Set Size)内存明显增加;

这种情况比较少见,是由于分配了大量小对象造成的内存碎片。

三、问题举例

新版本加入了一些功能,开发人员估计新功能可能会分配几万到几十万字节的内存,因此进行内存方面的验证测试;

当新功能的代码合入后,发现应用启动后的内存增长超过了2MB,超出了预期。

如果某个新功能的代码都在同一个package下,那么就可以用MAT的过滤功能来验证这部分代码是否使用了内存。

1、内存中多出来一些新的对象,多消耗了300KB的内存

进一步确认这些对象是有用的,还是临时创建的;

对于临时创建不再使用的对象,可以主动销毁;

而对于保存着信息将要用到的对象也可以进行压缩裁剪,以进一步减少占用的内存。

以上清理不用的对象后,总体内存没有明显减少,看来在Dalvik Heap里分配的内存并没有增加许多,

说明问题是不能只在Dalvik Heap里就能解决的,也许是别的部分出现了问题;

2、新的问题:

以上优化后,Heap内存表现已经比较好了;

但Heap内存并不是应用的全部,我们在设置或其他应用管理工具里看到的应用内存大小是应用整个进程的内存使用量。

也有可能出现Hep部分完全没有增长,而其他部分增长的情况。

要观察应用进程的内存使用情况,就需要用到其他的观测工具;

Android里最常用于观察进程内存的方法就是以下命令:

adb shell dumpsys meminfo <packaga name| pid>

Pss列的数据---标识进程各部分对真实物理内存的消耗;

左下角的Total值就是我们在各种管理工具里看到的应用内存消耗;

而Android studio等工具里显示的内存值,在这里是Dalvik Heap Alloc部分;

因此看出Dalvik Heap和Heap Alloc不是相等的,而且除了Dalvik Heap以外,还有其他很多部分也会消耗内存。

对比新旧版本,排查发现Heap Alloc没有增加多少,但Dalvik Heap Pss增加了许多,可见问题还是出现在Dalvik Heap部分,但只靠检查分配的对象是看不出来问题的。

3、新问题的进一步挖掘:

Java代码的内存分配和释放都是由虚拟机管理的,那么这个问题会是虚拟机的问题吗?

接下来通过虚拟机部分机制来探索内存增长的原因。

新版本的Dalvik Heap Pss内存出现了2MB左右的增长,但Dalvik Heap Alloc只增长了273KB,而从Dalvik Heap Free也看出大部分增长的内存是处于空闲状态。

经过观察,有以下几点发现:

(1)经过较长时间待机后,也没有被释放回系统;

(2)有几处代码会导致内存增长,只要将这些代码屏蔽掉,内存使用情况就下降到正常水平;

(3)这些代码分配的内存并不多,甚至有些地方是不需要分配内存的;

(4)有些代码并不是这个版本新加入的,已经存在较长时间了;

(5)使用裁剪功能的方法编译并分析内存后,基本可以确定是新加入的代码消耗了内存,但并没有内存泄漏,代码经过审查也没有发现问题;

接下来从更底层的DVM虚拟机寻找问题。

(1)Dalvik Heap内部机制

为了弄清楚DVM为什么占着内存不释放,阅读DVM分配内存部分的代码,位置在Android源码的dalvik/vm/alloc下;

a. DVM使用mmap系统调用从系统分配大块内存作为Java Heap;

而根据系统机制,如果分配的内存尚未真正使用,就不计入PrivateDirty和Pss;

根据数据Heap Size/Alloc很多,但大部分是共享的,实际使用的较少,反映到PrivateDirty/Pss里的内存并不多。

b.新建对象后,由于要向对应的地址写入数据,内核开始真正分配该地址对应的4KB物理内存页面;

c.运行一段时间后,开始垃圾回收(GC),有些对象被回收了,有些会一直存在;

d.在GC时,有可能会进行trim,即将空闲的物理页面释放回系统,表现为PrivateDirty/Pss下降;

(2)问题所在

在了解DVM分配释放内存机制后,根据dumpsys观察到的现象,猜测可能出现了页利用率问题(页内碎片)。

这种情况下可能产生的问题时,整页4KB的内存可能只有一个小对象,但统计PrivateDirty/Pss时还是按4KB计算。

在通常的JVM中,借助Compacting GC机制,整理内存对象,将散布的内存移到一起;

但根据DVM的代码,DVM的Mark-Sweep算法不能移动对象,即没有内存整理功能,这种情况下就会形成内存空洞。

以上时问题原因的猜测,需要验证是否时猜测的原因;由于MAT的对象实例数据中有地址和大小信息,先从MAT中导出数据;

在MAT中列出所有对象实例:list_object java.*,然后选中所有数据并导出为csv格式;

处理导出的csv文件,按页面进行统计,

取每个对象的地址的高位(&0xffff0000),结果相同的对象处在同一页面中。

最后按每个页面所有对象的大小分类统计,绘制出直方图;

得到被测应用的页面利用率分布图,发现利用率低的页面数目增加,说明小对象碎片的数量增加了。

(3)优化Dalvik内存碎片 

为了找出有问题的代码,将上一步得到的数据继续处理;

取出所有使用不满2KB的页面的内存地址,再使用OQL将地址导入到MAT中,分析地址对应的对象是什么;

就能看出来是哪些对象造成了内存的碎片化,数量比较多的前几类嫌疑较大,可以先对前几个类的代码进行分析;也可以对这些代码进行针对性的内存测试。

还原出问题的基本过程:

a.生成对象的过程需要较多的临时变量;

b.批量生成的过程,由于还有空闲内存,虚拟机没有做垃圾回收;

c.完成后才进行垃圾回收,清除了所有的临时变量,留下碎片化内存;

4、经验总结

对于测试人员来说:

(1)MAT是探索Java堆并发现问题的好帮手,能够迅速发现常见的图片和大数组等问题;但也不是万能;

(2)对Android测试经验来说,容易找到应用代码及框架的各种测试经验和指导;

底层以及涉及性能的测试可以借鉴Linux系统的测试经验,了解内核及进程相关的知识,熟悉常用工具;

(3)内存分配的最小单位页面,通常为4KB;

对于开发人员:

(1)尽量不要在循环中创建很多临时变量;

(2)可以将大型的循环拆散、分段或按需执行;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值