Android 应用性能测试——内存篇
Android内存监控与分析三部曲(一)--最常遇见的内存泄漏测试场景
Android内存监控与分析三部曲(二)--Java内存管理机制
Android内存监控与分析三部曲(三)--Android的内存管理
APP测试中难免会有各种显式或者隐式的内存泄漏(Memory Leak)问题,如果不及时发现处理,可能会因为内存泄漏导致各种奇怪的问题(如,卡顿和闪退),甚至可能出现因内存不足(Out of Memory,简称OOM)而导致APP崩溃。
本文分为三部分,通过实战分析内存泄漏和内存溢出问题,并在必要时说明原理或机制。三部曲快速准确定位常见的内存问题。即,
(一)内存泄漏与内存溢出的表现形式和最常遇见的内存泄漏测试场景
(二)分析内存泄漏的原理
(三)内存分析实例演示
图1 内存监控与分析
内存分析实例演示
一、内存测试流程中的要点
1. 代码
通常用来进行内存测试的版本是纯净版本,不应该附加多余的Log和调试用组件。例如有些情况下,为了测试界面延迟/函数执行时间等性能,会加入一些桩点代码。在内存测试中这些代码是不必要的,它们可能会分配临时内存,引起更多的GC,导致应用出现运行缓慢、卡顿等现象。
2. 测试场景
测试场景通常有两类。
一类是当前有新开发或改动的某项功能,需要对该功能进行性能测试。因此测试场景主要针对该功能组织,包括功能的开启前、运行、结束后等测试点。
另一类是整体性能,考察应用的常见场景,在综合使用情况下的性能指标。测试场景应当包括启动后待机,切换到后台,执行主要功能,以及反复执行各功能后。
在各类场景中,经常作为测试重点的有:
在各类场景中,经常作为测试重点的有:
- 包含了图片显示的界面。
- 网络传输大量数据。
- 需要缓存数据的场景。
3. 场景转换成用例
选取了测试场景后,用例设计也要考虑内存测试的特点。一些常见的方法是:
- 结合场景比较操作前后或不同版本的内存变化。
- 显示多张图片的前台进程。
- 多个场景来回切换。
- 长时间运行进程的内存增长。
4. 执行
由于GC和广播机制的存在,应用内存通常都在不停地波动,幅度可能会达到几百KB,因此执行时需要考虑这种情况。在采集数据时,需要多次采集并计算平均值。
执行完成,我们就可以根据数据进行比较初步的分析以确定方向。一方面是我们熟悉的Dalvik Heap部分,即由Java代码直接分配的内存,可以通过IDE直接观察到使用情况,也可以使用MAT进行细致的分析。
另一方面,假如我们发现Dalvik Heap没怎么增长,而其他部分增长了许多,这种情况下的分析就要复杂一些。
排除方法通过logcat命令输出的log信息,搜关键词“GC”。如果有下面四个中的一个,就可能存在内存泄露。
GC_FOR_ALLOC:因为在分配内存时内存不够引发的;
GC_EXPLICIT:表明GC被显式请求触发的,如Runtime.gc()或VMRuntime.gc()调用;
GC_CONCURRENT:表明GC在内存使用率达到一定的警戒值时,自动触发;
GC_BEFORE_OOM:表明在抛出内存溢出(OOM)异常之前,尝试执行最后一次内存回收。
二、实战演示(TestMemory.apk实例)
Java堆用于存储对象实例,我们只有不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。
图五、六操作导致的内存泄漏部分代码如下:
在一个MainActivity,实现了listeners。 这个MainActivity中,addMemory.setOnClickListener创建一个大的字符串数组,不断点击此按钮导致内存泄漏;持续点击直到内存溢出。在跳转页面clickToSecondPage.setOnClickListener后,staticActivity 引用MainActivity导致内存泄漏。
分析TestMemory内存泄漏任务包括:
1) 检测泄露的Activity实例
2) 查找重复的String实例
HPROF Viewer界面,分析内存泄漏。如图20:
图20 内存泄漏分析
第一步➡️
点击“Analyzer Tasks”视图中的启动按钮,启动分析。
第二步➡️
查看“Analysis Result”中的分析结果,点击“Leaked Activities”中的具体实例,该实例的引用关系将会展示在“Reference Tree”视图中。
第三步➡️
根据“Reference Tree”视图中的引用关系找到是谁让这个leak的activity活着的,也就是谁支配(Dominate)这个activity对象。
此例中, 比较简单,可以很清晰看到是this的实例最终支配了MainActivity。“this”实例连接到GC Roots,故而导致MainActivity GC Roots可达,无法被回收。
上述步骤, 可以让我们快速定位可能的内存泄露。 当然,内存问题,除了内存泄露,还有内存消耗过大。我们可以在Heap Viewer中查看分析内存的消耗点。
使用Heap Viewer查看内存消耗。如图21:
图21 Heap Viewer查看内存消耗
第一步➡️
点击视图中“Package Tree View”的按钮, 使用包视图,可以让我们关注到自动APP相关对象的实例。
第二步➡️
点击“Retained Size”排序, 快速找到内存消耗点。
第三步箭头
找到最大内存消耗点。
总结
分析内存问题, 主要是观察和比较内存增长情况。然后,分析对象的内存占用(Retained Size)情况,找出Retained Size较大的对象,找到其直接支配者(Immediate Dominator),跟踪其GC可达路径(Path to GC Roots),从而找到是谁让这个大对象活着。