应用版本升级后使用内存突增?如何跟踪?这次MIG专项测试组为大家分享内存问题跟踪实战过程!
致力于为腾讯移动互联网事业群(MIG)提供专项评测及深度优化(性能、功能、安全等);同时负责探索新的测试理论和方法,研发评测工具及基础组件。
手机管家从4.4升级到4.5后,用户数据反馈待机内存出现了2-4M左右的增长。经过代码排查及MAT分析,发现有几处代码会导致内存增长,只要将这些代码屏蔽掉一部分,内存情况就下降到正常水平。
奇怪的是这些代码在使用过程中分配的内存并不多,只有上百K,甚至有些地方是基本不需要分配内存,但为什么会导致2-4M的内存增长?
我们观察了不同版本的meminfo的区别,发现内存的主要增长点是Dalivk部分:
从上图可以看到,在Heap Alloc增长273K的情况下,Pss有1M的增长。从HeapFree看出大部分增长的内存是空闲的,而且经过较长时间待机后也没有被释放回系统。
对于Dalvik内存问题,通常先使用MAT辅助分析原因,要关注的有以下几项:
经过MAT及缩减编译分析后,基本可以确定是新代码消耗了内存。但却没有发现有明显内存泄漏的地方,而且代码经过review也没有发现问题。
这个结果让我们陷入了困惑,常用的方法找不出问题,说明有更深层次的原因。接下来要从更底层的DVM虚拟机寻找问题。
为了弄清楚为什么DVM占着内存不释放,我们阅读了DVM分配内存部分的代码。代码位置在Android源码的dalvik/vm/alloc下,约255K。分析出的主要流程如下。
1、DVM使用mmap/sbrk从系统分配大块内存作为Java Heap。根据系统机制,如果分类的内存尚未真正使用,就不计入PrivateDirty和PSS。
例如下图,Heap Size/Alloc很多,但大部分是共享,实际使用的较少。所以反映到PrivateDirty/PSS里的内存并不多。
2、New对象之后,由于要向对应的地址写入数据,内核开始真正分配该地址对应的4K物理内存页面。
Alloc.cpp,176行:
3、运行一段时间后,开始GC,有些对象被回收了,有些会一直存在。
4、在GC时,有可能会进行trim。即将空闲的物理页面释放回系统,表现为PrivateDirty/PSS下降。
HeapSource.cpp,431行和1304行:
释放时是以4K物理页面为单位:
在了解DVM分配释放内存的机制后,根据meminfo观察到的现象,猜测可能出现了页利用率问题(页内碎片)。如下图所示,
第一行:在开始阶段,内存分配的较满。第二行:经过GC后,大部分对象被释放,少部分留下来。
这种情况下可能会产生的问题是,整页的4K内存中可能只有一个小对象,但统计PrivateDirty/PSS时还是按4K计算。
在通常的jvm虚拟机中,有Compacting GC机制,整理内存对象,将散布的内存移动到一起。但根据DVM的代码,DVM的Mark-Sweep算法不能移动对象,即没有内存整理功能,这种情况下就会形成内存空洞。
在猜测了可能的问题后,需要验证是否如猜测原因所致,为此我们需要获取dvm虚拟机的底层内存分配数据,然后按每个物理页面统计所有对象的大小。
在阅读代码的过程中,发现DVM有个内部函数能够遍历所有的内存块,正好能实现我们的需求。
HeapSource.h,161行:
接下来的工作就是想办法在native层调用dvmHeapSourceWalk函数,并将我们的回调函数传进去。回调函数记录下来每块内存的地址和大小,保存下来的数据是这样的:
page_start_addr;chunk_start_addr;chunk_end_addr;object_size;chunk_size 41708000;1097891848;1097892056;208;212 41708000;1097892064;1097892320;256;260 41708000;1097892328;1097892488;160;164 41708000;1097892496;1097892656;160;164 41708000;1097892664;1097892968;304;308 41708000;1097892976;1097893136;160;164
再将这些数据按4K页面的范围进行累加,统计每个4K页面的使用率,做出直方图。
由此可见,4.5版本相比以前,不满的页面变多了。这就会造成开头说的现象,Heap Free和PSS都增加,很长时间也不会释放。
为了找到出问题的代码,我们在上一步得到的数据上继续处理。取出所有使用不满2K的页面的内存块地址,再使用OQL将地址导入到MAT中,分析地址对应的对象是什么。
对上图得到的对象实例计算dominators。
在这里基本就能看出来是哪些对象造成了内存的碎片化。通过对生成这些对象的代码分析和模拟实验,还原出基本的过程:
下图是模拟这个过程的代码,执行这段代码将会在内存中形成很多碎片,造成很高的PSS占用。
除了在微信公众号为大家定期分享移动质量分析干货外,腾讯Bugly最近开始筹办移动开发者沙龙活动,希望与更多的移动开发者进行面对面交流。
不总结哪来经验,不分享经验何用?
在此小编号召大家多总结,互分享,踊跃给我们投稿,把自己踩过并爬出来的坑树个指示牌警醒后人,让猿们的开发生活更加美好!
投稿方式:将文章和个人介绍邮件到 bugly@tencent.com,字数不限。
本文系腾讯Bugly特邀文章,转载请注明作者和出处“腾讯Bugly(http://bugly.qq.com)”