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

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

背景
手机管家从4.4升级到4.5后,用户数据反馈待机内存出现了2-4M左右的增长。经过代码排查及MAT分析,发现有几处代码会导致内存增长,只要将这些代码屏蔽掉一部分,内存情况就下降到正常水平。
奇怪的是这些代码在使用过程中分配的内存并不多,只有上百K,甚至有些地方是基本不需要分配内存,但为什么会导致2-4M的内存增长?
初步分析
我们观察了不同版本的meminfo的区别,发现内存的主要增长点是Dalivk部分:



从上图可以看到,在Heap Alloc增长273K的情况下,Pss有1M的增长。从HeapFree看出大部分增长的内存是空闲的,而且经过较长时间待机后也没有被释放回系统。
对于Dalvik内存问题,通常先使用MAT辅助分析原因,要关注的有以下几项:
1.使用bitmap插件查看是否有多余的图片没有释放
2.查找占大块内存较多的对象
3.查找其它不需要的对象造成的内存泄漏
4.结合代码改动,进行缩减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。



在这里基本就能看出来是哪些对象造成了内存的碎片化。
通过对生成这些对象的代码分析和模拟实验,还原出基本的过程:
1.生成对象过程需要较多的临时变量
2.批量生成过程中,由于还有空闲内存,虚拟机没有做GC
3.完成后才进行GC,清除了所有的零时变量,留下碎片化的内存
下图是模拟这个过程的代码,执行这段代码将会在内存中形成很多碎片,造成很高的PSS占用。



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


文章来自:腾讯Bugly
  • 大小: 35 KB
  • 大小: 35.7 KB
  • 大小: 33.1 KB
  • 大小: 110.1 KB
  • 大小: 32.2 KB
  • 大小: 63.5 KB
  • 大小: 98.2 KB
  • 大小: 7.5 KB
  • 大小: 50.9 KB
  • 大小: 86.7 KB
  • 大小: 97.9 KB
  • 大小: 107.1 KB
  • 大小: 22.2 KB
  • 大小: 22.2 KB
来自: 腾讯Bugly
1
1
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • C语言和C#中文件打开方式的详解

    通过以上示例代码,我们可以看到在C语言和C#中,打开文件的方式略有不同,但基本思路是类似的。我们需要使用相应的函数或类来打开文件,并进行后续的读取或写入操作。在实际应用中,我们可以根据具体需求选择合适的打开方式和模式。文件打开是其中一个基本的操作,它允许我们读取或写入文件的内容。本文将详细介绍C语言和C#中文件打开的几种方式,并附带相应的源代码示例。希望本文能够帮助你理解C语言和C#中的文件打开操作,并能顺利地进行文件处理。类提供了多个静态方法来实现文件的打开操作。函数接受两个参数:文件路径和打开模式。

  • Csharp__WinForm_打开一个进程、文件等.zip

    在WinForm/C#中打开一个文件,主要是用到进程的知识。 下面是一些实例,可以模仿着去实现。 1. 打开文件 2.打开浏览器 3. 打开指定URL 4. 打开文件夹 5.打印文件

  • 如何使用最佳方法在没有Outlook的情况下查看/打开PST文件

    在本文中,您可以学习如何使用 DIY 方法通过简单的步骤在没有 Outlook 的情况下打开 PST 文件。有一些简单的方法可以打开 PST 文件和详细查看 PST 文件的电子邮件,本文中给出了这些方法。如果要在不使用 Microsoft Outlook 的情况下查看 PST 文件,可以使用第三方 PST 查看器工具。注意:如果您的PST文件已损坏或无法访问,那么您可以尝试使用第三方Outlook PST恢复工具,该工具可以在没有Outlook的情况下查看或打开您的PST文件。

  • C#调用系统默认程序打开文件(在自己的程序外部打开)

    所有有默认打开程序的文件都可以打开,没有默认打开程序的需要选择打开程序。

  • C#实现调用外部exe几种方式(附完整源码)

    C#实现调用外部exe几种方式(附完整源码)

  • c#调用外部文件

    说来遗憾,以前帮老师做的那个《数据挖掘--重叠聚类》的软件真心觉得不错、还有专利权。。o(︶︿︶)o 。。可惜后来很长一段时间以后一不小心自己重装还原了系统后丢失找不到了。。。所以那叫一个悔恨呐。。所以。。在进行一个项目的同时,网上记录、思考和总结是很有必要的。。 这两天自己闲来无事,做了个桌面软件。调用了一些外在程序。总结如下: 1、C#调用EXE文件  System.Diag

  • C# 打开文件和打开文件夹(含源码工程)

    C# 打开文件和打开文件夹(含源码工程)

  • C# 客户端程序调用外部程序的3种实现方法

    第一种是利用shell32.dll,实现ShellExecute方法,该方法可同时打开本地程序、文件夹或者访问网站,只要直接输入路径字符串即可,如C\Users\Desktop\xx.exe或者https//cn.bing.com/,可以根据返回值判断是否调用成功(成功0x00000002a,失败0x00000002)第三种方法是利用Process类,Process类具体应用可以看类的定义,这里只实现它打开文件和访问网站的用法,调用失败会抛出异常​​​​​​。...

  • C# 打开文件和显示读取的文件内容

    C# 打开文件和显示读取的文件内容1. 打开文件所在的路径2. 根据现有的路径打开文件3. 打开HEX文件并且转换成bin文件3.1 Hex文件各部分的意义 1. 打开文件所在的路径 通过点击按键,打开打开文件的界面,点击后可以在 richTextBox2上显示 private void button1_Click(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDia

  • C# 对文件、文件夹的操作

    C# 对文件、文件夹的操作

  • C#进行文件的操作

    一、打开文件对话框,选择txt文件进行内容的读取。二、选择图片内容,显示所选择的图片。三、遍历一个文件夹里的内容。

  • C#打开外部文件,如txt文件

    string filePath = @"d:\test.txt"; if (filePath.Equals("")) { MessageBox.Show("路径不能为空!","操作提示"); return; } //先判断文件是否存在,不存在则提示 if (!System.IO.File.Exists(filePath))...

  • c# 打开外部可执行文件exe。

    一、打开程序。     先介绍    System.Diagnostics.Process类:用来启动和停止进程的。         1、            Process pr = new Process();//声明一个进程类对象             process.StartInfo.FileName = "C:\\Keil_v5\\UV4\\UV4.ex

  • C#打开一个文件的操作详解

    <br />C#打开一个文件的操作在我们实际的开发中是常见的需求实现,那么具体的操作实例是什么呢?C#打开一个文件所涉及的问题和注意事项有哪些呢?现在我们就来看看具体的实现:<br />在你写入任何实际数据之前,你可能希望通过以下几种方法写入一些关于本文档的摘要:public boolean addTitle(String title)  public boolean addSubject(String subject)  public boolean addKeywords(String keywords

  • C# 打开文件

    C# 打开文件

  • C#打开docx文件 (其他文件类似)

    private void label20_Click(object sender, EventArgs e)         {             string path = String.Empty;             path = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + @"HelpD

  • C#判断文件是否被打开

    C#判断文件是否被打开,以前自己用的,似乎也是从网上找来的。

  • c# 打开文件

    string AttchFileName = "C:\test.pdf"; 使用系统默认打开方式打开文件: System.Diagnostics.ProcessStartInfo Info = new System.Diagnostics.ProcessStartInfo(AttchFileName); System.Diagnostics.Process Pro = System.Dia

  • C#使用进程打开文件

    进程 1.一个应用程序就是一个进程,而一个进程又是由多个线程组成的。 2.进程帮助我们在内存中分配应用程序执行所需要的空间。 3.我们可以通过进程来直接操作应用程序。 代码操作 获取当前所有进程名 //存储着我们当前正在运行的进程 Process[] process = Process.GetProcesses(); foreach (var...

Global site tag (gtag.js) - Google Analytics