本文来自于腾讯优测公众号(wxutest),未经做者赞成,请勿转载,原文地址:https://mp.weixin.qq.com/s/8BiKIt3frq9Yv9KV5FXlGwjava
在上一节里,咱们介绍了内存测试的基本流程,讲述了如何发现并处理简单的内存问题。对于Dalvik Heap部分总结出了一些常见的问题模式,以及如何使用工具识别和处理这些常见的内存问题。算法
当简单问题再也不是问题的时候,咱们就会开始赶上一些奇怪问题了,相似于下面这些:数组
“咱们这个版本引入了一个挺简单的库,内存就涨了2M” “这些代码只是初始化了几个对象,尚未开始用呢” “我只是改了一行代码,没有建立新对象” “我一行代码都没改,怎么会涨呢”微信
此次出现的问题就是这样这一类问题,新版本的Dalvik Heap Pss内存出现了2M左右的增加。但Dalvik Heap Alloc只增加了273K的状况下。而从Dalvik Heap Free也能看出大部分增加的内存是空闲状态的。并发
通过一段时间对问题的观察,咱们有如下几点发现:app
这个结果让咱们陷入了困惑,经常使用的方法找不出问题,说明有更深层次的缘由。接下来要从更底层的Dalvik虚拟机寻找问题。框架
为了弄清楚为何DVM占着内存不释放,咱们阅读了DVM分配内存部分的代码。位置在Android源码的dalvik/vm/alloc下,约255K。分析出的主要流程以下:jvm
1) DVM使用mmap系统调用从系统分配大块内存做为Java Heap。根据系统机制,若是分类的内存还没有真正使用,就不计入PrivateDirty和PSS。例如图1-8,Heap Size/Alloc不少,但大部分是共享,实际使用的较少。因此反映到PrivateDirty/PSS里的内存并很少。工具
图1-8 共享内存较多的进程性能
2) New对象以后,因为要向对应的地址写入数据,内核开始真正分配该地址对应的4K物理内存页面。
Alloc.cpp, 176行起:
图1-9 DVM虚拟机分配内存的代码
3) 运行一段时间后,开始GC,有些对象被回收了,有些会一直存在,如图1-10所示。
图1-10黑点表示的内存会被回收
4) 在GC时,有可能会进行trim。即将空闲的物理页面释放回系统,表现为PrivateDirty/PSS降低。
HeapSource.cpp, 431行:
图1-11 释放内存回系统的代码(一)
HeapSource.cpp,1304行:
图1-12 释放内存回系统的代码(二)
在了解DVM分配释放内存的机制后,根据dumpsys观察到的现象,猜想可能出现了页利用率问题(页内碎片)。如图1-13所示,第一行:在开始阶段,内存分配的较满。第二行:通过GC后,大部分对象被释放,少部分留下来。
图1-13产生内存碎片
这种状况下可能会产生的问题是,整页的4K内存中可能只有一个小对象,但统计PrivateDirty/PSS时仍是按4K计算。
在一般的jvm虚拟机中,有Compacting GC机制,整理内存对象,将散布的内存移动到一块儿。但根据DVM的代码,DVM的Mark-Sweep算法不能移动对象,即没有内存整理功能,这种状况下就会造成内存空洞。
在猜想了可能的问题后,须要验证是否如猜想缘由所致,因为MAT的对象实例数据中有地址和大小信息,咱们先从MAT中导出数据。
在MAT中列出全部对象实例:list_objects java.*,而后选中全部数据导出为CSV格式,以下所示:
Class Name,Shallow Heap,Retained Heap, class java.lang.Class @ 0x41fdd1e8,16,56, class test.bxi$3 @ 0x432501c8,0,0, class test.aaw$c$1 @ 0x4324fef8,0,0, class test.ds @ 0x4324fc88,8,48, class test.bxh @ 0x4324f438,8,248, class test.bxg @ 0x4324f248,0,0, class test.bxd$1 @ 0x4324f028,0,0,
处理导出的csv文件,按页面进行统计,取每一个对象的地址的高位(&0xfffff000),结果相同的对象处在同一页面中。最后再按每一个页面全部对象的大小分类统计,作出直方图如图1-14所示。
图1-14对页面利用率进行分类统计
这张图就是被测应用的页面利用率分布图,左边是利用率低的页面,右边是利用率高的页面。若是发现利用率低的页面数目增长,说明小对象碎片的数量增长了。
为了可以找出有问题的代码,咱们将上一步获得的数据继续处理。取出全部使用不满2K的页面的内存块地址,再使用OQL将地址导入到MAT中,分析地址对应的对象是什么。如图1-15所示就是将地址从新导入到MAT中获得的对象列表了:
图1-15内存碎片页中的对象
在这里基本就能看出来是哪些对象形成了内存的碎片化,数量比较多的前几个类的天然嫌疑比较大,能够先对前几个类的相关代码进行分析。也能够对这些代码进行针对性的内存测试,观察内存状况。
经过对生成这些对象的代码分析和模拟实验,咱们还原出问题的基本过程:
下图是形成这个问题的相似代码,执行这段代码将会在内存中造成不少碎片,形成很高的PSS占用。
private Object result[] = new Object[100]; void foo() { for(int i = 0; i < 100; ++i) { byte[] tmp = new byte[2000]; result[i] = new byte[4]; } }
图1-16显示了相似状况下数组的分配范围,可见数组中每一个成员的内存地址都是不连续的,而且相隔很远。这种状况下就会消耗不少个物理内存页面,增长Heap Free,形成例子中的问题。
图1-16内存碎片对象地址的例子
根据上述的流程,咱们搞清楚了形成问题的缘由,而且找到了问题代码。那么应当总结一些经验,以供借鉴。对于测试人员来讲,有如下两个经验:
对于开发人员,如下两个经验也许能有帮助:
更多精彩内容欢迎关注腾讯优测的微信公众帐号:
腾讯优测是专业的移动云测试平台,为应用、游戏、H5混合应用的研发团队提供产品质量检测与问题解决服务。不只在线上平台提供app自动化测试、云真机远程操控与调试、私有自动化测试工具XTest等多种质量检测工具,更为VIP客户配备了专家团队提供定制化综合测试解决方案。