【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虚拟机中,有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。

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

  • 生成对象过程需要较多的临时变量
  • 批量生成过程中,由于还有空闲内存,虚拟机没有做GC
  • 完成后才进行GC,清除了所有的零时变量,留下碎片化的内存

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

总结

  • 最好不要在循环中申请很多内存和创建很多临时变量;
  • 生成缓存的事,可以慢慢做,也可以按需缓存;
  • MAT不是万能的,比如这次的数据隐藏在每个对象的地址中;
  • 了解Linux系统内核对Android测试有帮助;
  • 内存分配的最小单位是页面,通常为4K;
  • 对于难缠问题,有必要从底层了解运作机制。

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

腾讯Bugly,最专业的质量跟踪平台

原文发布于微信公众号 - 腾讯Bugly(weixinBugly)

原文发表时间:2015-05-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ImportSource

并发编程-多线程的好处

上一文:并发编程-并发的简史 如果线程使用得当,多线程可以降低你的开发和维护成本,而且还能改善复杂应用程序的性能。多线程让模仿人类工作方式以及交互变得简单,多线...

37360
来自专栏IT技术精选文摘

程序员必知必会的那些邪恶的脚本

set -o errexit 等价于 set -e,表示有任何错误(命令的返回状态非 0 )时即退出。

14120
来自专栏后端技术探索

高并发场景:下单后定时发短信的问题

问题描述:让您做一个电商平台,您如何设置一个在买家下订单后的”第60秒“发短信通知卖家发货,您需要考虑的是 像淘宝一样的大并发量的订单。

13010
来自专栏Linyb极客之路

分布式之redis复习精讲

博主的《分布式之消息队列复习精讲》得到了大家的好评,内心诚惶诚恐,想着再出一篇关于复习精讲的文章。但是还是要说明一下,复习精讲的文章偏面试准备,真正在开发过程中...

18230
来自专栏Web行业观察

用Node.JS分析steam所有的游戏!

最近 Steam 玩得比较多,早晨突然想到一个有趣的问题:买下 Steam 所有游戏要花多少钱?

34020
来自专栏申龙斌的程序人生

零基础学编程004:集成开发环境IDE

几天前介绍了《用在线编程环境快速上手》学习Python等编程语言,这种教学环境中的例子都非常简单,你不需要在自己的电脑中安装任何的软件,就可以马上动手学习Pyt...

34050
来自专栏JAVA高级架构

分布式之redis复习精讲

16540
来自专栏来自地球男人的部落格

python中scrapy点击按钮

最初遇到的问题的是在用scrapy爬取微博时需要按照指定关键字来爬取特定微博,主要还是解决需要输入关键字然后点击搜索按钮的问题。于是: 首先 找了scrapy的...

43770
来自专栏芋道源码1024

告诉你 Redis 是一个牛逼货

Redis 是一个 Key-Value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、 list...

14800
来自专栏友弟技术工作室

程序员必知必会的那些邪恶的脚本

朝圣 前言 程序员必须掌握一定的运维知识。本文通过一些邪恶,搞破坏的方式。教会你一些危险的脚本操作。 附赠 运维意识与运维规范 1.线上操作规范 ...

35370

扫码关注云+社区

领取腾讯云代金券