【MIG专项测试组】实战分析:内存突增是为神马?

应用版本升级后使用内存突增?如何跟踪?这次MIG专项测试组为大家分享内存问题跟踪实战过程!


MIG专项测试组

致力于为腾讯移动互联网事业群(MIG)提供专项评测及深度优化(性能、功能、安全等);同时负责探索新的测试理论和方法,研发评测工具及基础组件。


背景

手机管家从4.4升级到4.5后,用户数据反馈待机内存出现了2-4M左右的增长。经过代码排查及MAT分析,发现有几处代码会导致内存增长,只要将这些代码屏蔽掉一部分,内存情况就下降到正常水平。

奇怪的是这些代码在使用过程中分配的内存并不多,只有上百K,甚至有些地方是基本不需要分配内存,但为什么会导致2-4M的内存增长?


初步分析

我们观察了不同版本的meminfo的区别,发现内存的主要增长点是Dalivk部分:


从上图可以看到,在Heap Alloc增长273K的情况下,Pss有1M的增长。从HeapFree看出大部分增长的内存是空闲的,而且经过较长时间待机后也没有被释放回系统。

对于Dalvik内存问题,通常先使用MAT辅助分析原因,要关注的有以下几项:

·        使用bitmap插件查看是否有多余的图片没有释放

·        查找占大块内存较多的对象

·        查找其它不需要的对象造成的内存泄漏

·        结合代码改动,进行缩减build,统计新代码消耗的内存

经过MAT及缩减编译分析后,基本可以确定是新代码消耗了内存。但却没有发现有明显内存泄漏的地方,而且代码经过review也没有发现问题。
这个结果让我们陷入了困惑,常用的方法找不出问题,说明有更深层次的原因。接下来要从更底层的DVM虚拟机寻找问题。


Dalvik Heap内部是如何分配和释放内存的?

为了弄清楚为什么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虚拟机中,有CompactingGC机制,整理内存对象,将散布的内存移动到一起。
但根据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



在这里基本就能看出来是哪些对象造成了内存的碎片化。
通过对生成这些对象的代码分析和模拟实验,还原出基本的过程:

·        生成对象过程需要较多的临时变量

·        批量生成过程中,由于还有空闲内存,虚拟机没有做GC

·        完成后才进行GC,清除了所有的零时变量,留下碎片化的内存


下图是模拟这个过程的代码,执行这段代码将会在内存中形成很多碎片,造成很高的PSS占用。



总结

·        最好不要在循环中申请很多内存和创建很多临时变量;

·        生成缓存的事,可以慢慢做,也可以按需缓存;

·        MAT不是万能的,比如这次的数据隐藏在每个对象的地址中;

·        了解Linux系统内核对Android测试有帮助;

·        内存分配的最小单位是页面,通常为4K

·        对于难缠问题,有必要从底层了解运作机制。


小编有话说

除了在微信公众号为大家定期分享移动质量分析干货外,腾讯Bugly最近开始筹办移动开发者沙龙活动,希望与更多的移动开发者进行面对面交流。

腾讯Bugly移动开发者沙龙

第一期 移动应用性能优化笔记 北京站
时间:2015年5月31日(周日)14:00 -18:00
地点:北京市海淀区 中关村创业大街 车库咖啡
活动现正火热报名中,欢迎大家报名参加!
点击这里,即可了解腾讯Bugly移动开发者沙龙活动详情,进行报名。


本文系腾讯Bugly特邀文章,转载请注明作者和出处“腾讯Bugly(http://bugly.qq.com)”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值