Android 应用性能测试——内存篇

Android内存监控与分析三部曲(一)--最常遇见的内存泄漏测试场景

Android内存监控与分析三部曲(二)--Java内存管理机制

Android内存监控与分析三部曲(三)--Android的内存管理

APP测试中难免会有各种显式或者隐式的内存泄漏(Memory Leak)问题,如果不及时发现处理,可能会因为内存泄漏导致各种奇怪的问题(如,卡顿和闪退),甚至可能出现因内存不足(Out of Memory,简称OOM)而导致APP崩溃。

本文分为三部分,通过实战分析内存泄漏和内存溢出问题,并在必要时说明原理或机制。三部曲快速准确定位常见的内存问题。即,

(一)内存泄漏与内存溢出的表现形式和最常遇见的内存泄漏测试场景

(二)分析内存泄漏的原理

(三)内存分析实例演示

androidstudio内存检测工具 android内存测试_内存泄漏

图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到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。

图五、六操作导致的内存泄漏部分代码如下:

androidstudio内存检测工具 android内存测试_内存泄漏_02

androidstudio内存检测工具 android内存测试_Android_03

 

在一个MainActivity,实现了listeners。 这个MainActivity中,addMemory.setOnClickListener创建一个大的字符串数组,不断点击此按钮导致内存泄漏;持续点击直到内存溢出。在跳转页面clickToSecondPage.setOnClickListener后,staticActivity 引用MainActivity导致内存泄漏。

分析TestMemory内存泄漏任务包括:

1) 检测泄露的Activity实例

2) 查找重复的String实例

HPROF Viewer界面,分析内存泄漏。如图20:

androidstudio内存检测工具 android内存测试_内存溢出_04

图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:

androidstudio内存检测工具 android内存测试_androidstudio内存检测工具_05

图21 Heap Viewer查看内存消耗

第一步➡️

点击视图中“Package Tree View”的按钮, 使用包视图,可以让我们关注到自动APP相关对象的实例。

第二步➡️

点击“Retained Size”排序, 快速找到内存消耗点。

第三步箭头

找到最大内存消耗点。

总结

分析内存问题, 主要是观察和比较内存增长情况。然后,分析对象的内存占用(Retained Size)情况,找出Retained Size较大的对象,找到其直接支配者(Immediate Dominator),跟踪其GC可达路径(Path to GC Roots),从而找到是谁让这个大对象活着。