本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/01Abwe0p1h3WLh28Tzg_Dw

1.5案例:优化dex相关内存

在上一节我们提到,随着代码功能的增加,代码复杂度也在不断地变大,这时候我们往往会发现Dalvik Other和dex mmap这两部分消耗的内存也在不断的增加。在之前的例子里,我们知道这两部分的内存已经接近总内存的一半。在Dalvik Heap已经充分优化的情况下,我们有必要继续对这部分内存进行研究如何优化。

我们已经知道Dalvik Other存放的是类的数据结构及关系,而dex mmap是类函数的代码和常量。通常情况下,想要减少这部分的内存,需要从代码出发,精简无用代码,或者将功能插件化。但如果我们深入理解了系统,也能够找到一些其他的方法来降低这部分的内存消耗。

1.5.1 从class对象说起

在MAT的对象实例列表中,我们往往能很多Class条目,如图1-16所示:

图1-16 MAT中似乎不消耗内存的Class类

这些对象是各种类型的元数据。从MAT的信息看来,它们只是保存了各个类的静态成员,所以对于没有静态成员的类型,shallow heap的值为0,并不消耗内存。但实际上,这只是class消耗内存的冰山一角。我们从下面的例子开始:

这段代码是一个数学处理库提供的函数,代码十分简单,只是新建了两个对象,但将这段代码在一个空应用中执行后,我们能够观察到以下的内存增长:

  • Dalvik heap增长约1.8M
  • Dalvik other增长约60K
  • .dex mmap增长约300K

Dalvik Heap的增长是我们能预期的。通常来说,能够从代码的逻辑中分析出执行这段代码总共需要分配多少内存,也能够在MAT中看到new对象消耗的内存。当应用使用完new的对象后,就会将heap内存释放,但dalvik other和.dex mmap部分是不会释放的。接下来我们首先分析一下这两部分为什么消耗了这么多内存。

1.5.2 一个类的内存消耗

首先,如果我们在代码中要使用一个类,例如以下代码:

Foo f = new Foo();

虚拟机在执行到这步时会做什么呢?

第一步是loadClass操作,将类信息从dex文件加载进内存:

1)读取.dex mmap中class对应的数据。

2)分配native-heap和dalvik-heap内存创建class对象。

3)分配dalvik-LinearAlloc存放class数据。

4)分配dalvik-aux-structure存放class数据。

第二步是new instance操作,创建对象实例。

1)执行.dex mmap中和的代码。

2)分配dalvik-heap创建class对象实例。

在这个过程中,可能还会分配dalvik-bitmap和jit-code-cache内存。如果class Foo引用了其他类型,那就还需要先按照同样的逻辑创建被引用的class。由此可见,在创建一个类实例的每一步都需要消耗内存。我们接下来大概计算一下new操作需要消耗的内存。

根据Dalvik虚拟机的代码,能够得知class根据类成员和函数的数目分配LinearAlloc和aux-structure的多少,以及class本身及函数需要的字节数。我们再根据一个应用中所有class的总量进行平均计算,得到以下一组数据:

第一步是loadClass操作,加载类信息。

  • .dex mmap(class def + class data): 载入一个类需要先读取259字节的mmap。
  • dalvik-LinearAlloc: 在LinearAlloc区域分配437字节,存放类静态数据。
  • dalvik-aux-structure: 在aux区域分配88字节,存放各种指针。

第二步是new instance操作,创建对象实例。

  • .dex mmap(code):为了执行类构造函数,还需要读取252字节的mmap。
  • dalvik-heap: 根据类的具体内容而变化。

可见在new对象实例的操作中,dalvik other和dex mmap部分就各需要约500字节的内存空间。但是考虑到4K页面的问题,由于这些内存并不是连续分布的,所以可能需要分配多个4K页面。当然由于很多类会在一起使用,使得实际的页面值不会那么多。

以我们举例的应用为例,总共有7042个类,启动后载入了1522个类,这时侯应用的dex mmap内存消耗大约是5M,平均后约为3.4K。Dalvik other的部分会少一些,但依然是远远超出需要使用的大小。

1.5.3 Dex mmap

dex mmap在Android应用中的作用是映射classes.dex文件。dalvik虚拟机需要从dex文件中加载类信息,字符串常量等;还需要在调用函数的时候直接从mmap内存中读取函数代码(dvm bytecode)来执行,所以该部分内存是程序运行必不可少的。

以一个示例应用为例,我们能够在MAT中看到,应用加载了大约1500个class类型,而dex文件的class类型共有10635个。使用dex mmap动态统计功能统计后发现,虽然只加载了1500个类,但dex内存通常却高达4-6M,差不多是dex文件大小的一半。如表1-1所示。

表1-1 dex内存的利用率

以上数据中可以看到,很大一部分dex内存空间被浪费了,实际使用到的数据和代码并没有那么多,这是为什么呢?这是由于dex文件在生成时是按字母顺序排列。由于4K页面加载的原因,实际运行时会加载许多相邻但不会被用到的数据。例如在代码中使用了A1类,虚拟机就需要加载包含A1类数据的页面。但由于A1的数据只有1K,那在加载的4K页面中,还会有A2A3A4类,总共占用了4K内存。

假设我们的代码里在用到A1类后,还会用到B1C1D1类,那么如果能在dex文件中将A1B1C1D1类放在一起,虚拟机就只需要加载一个4K页面,不仅减少了内存使用,还对程序的运行速度有好处。因此,优化的思路就是调整Dex文件中数据的顺序,将能够用到的数据紧密排列在一起。

1.5.4 Dex文件优化

为了达到优化的目的,我们需要先了解Dex文件的结构。Dex文件结构如表1-2所示:

表1-2 dex文件结构

简而言之,为了节约空间,dex将原先在各个class文件中重复的信息集中放置在一起,并以索引和指针的形式支持快速访问。虚拟机能够通过索引表在Data区域中找到需要的信息。下面我们看一个访问字符串的例子:

在dex文件结构中,读取字符串需要先到StringIdList中查表,然后根据查到的地址到Data区读取内容。StringIdList的数据结构如下:

struct DexStringId {
u4 stringDataOff;
};

现在我们模拟虚拟机读取一个字符串,来观察内存的消耗。

假设有一个字符串的id = 6728,对应的地址就会是112 + 6728 = 6990。因此虚拟机首先根据string id读取0x006990 - 0x006994的内容,此时系统会加载0x006000-0x006fff的整页内存,从PSS角度来看,会增加4K。

虚拟机读到的内容是stringDataOff = 0x531ed4,随后虚拟机会继续从0x531ed4读取字符串内容,假设字符串长度是45字节,则虚拟机会读取0x531ed4 - 0x531f04的内容,但此时系统也必须加载0x531000 - 0x531fff的整页内存,从PSS角度来看,会再次增加4K。

由此可见,在有些情况下,虚拟机读取data区的一个数据,就至少要消耗8K物理内存。如果多次读取的分散在文件各处的数据,就可能会以4K的倍数快速消耗内存。

Android SDK提供了dexdump工具来观察Dex文件内容,我们以此工具来看看Dex的数据内容:

dexdump classes.dex
Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'
Class #0 header:
...
Class #0 -
Class descriptor : 'Laaa/aaa;'
...
Class #1 -
Class descriptor : 'Laaa/bbb;'
...
Class #2 -
Class descriptor : 'Lbbb/ccc;'
...

根据对Dex数据的观察,我们发现Dex文件中数据基本是按类名的字母顺序进行排列的,这样同样包名的类会排在一起。但在实际程序执行中,同一个package下的类并不会全部一起调用,而是和很多其他package下的类进行交互,但mmap加载了整个页面,可能会有很多无用数据。为了减少这样的情况,我们在生成文件时要尽量将使用到的数据内容排布在一起。在APK的编译流程中,Proguard混淆工具正好是能够对类名进行修改的,可以根据程序运行的逻辑,将那些会互相调用的类改为同一个package名,这样就可以使它们的数据排布在一起。

以上表数据为例,Class的排列顺序是aaa/aaa,aaa/bbb,bbb/ccc。假设我们的应用运行逻辑是aaa/aaa,bbb/ccc,而aaa/bbb在某些特殊时候才能用到。但在当前的排列情况下,加载了aaa/aaa和bbb/ccc就必然要加载aaa/bbb。我们可以用Proguard等工具来控制类名,将aaa/bbb等不常用的类放在后面,则aaa/bbb平时就不会加载。如下表所示:

dexdump classes.dex
Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'
Class #0 header:
...
Class #0 -
Class descriptor : 'La0;' # 原aaa/aaa
...
Class #1 -
Class descriptor : 'La1;' # 原bbb/ccc
...
Class #2 -
Class descriptor : 'La2;' # ...
...
Class #100 -
Class descriptor : 'La100;' # 平时用不到的aaa/bbb
...

经验总结

根据上述的流程,我们探索了Dalvik Other和dex mmap部分的内存,大致搞清楚啦它们被消耗的机制,以及一些能够减少消耗的方法。经验如下:

  • 在优化内存时,不只有堆内存,还有其他许多类型的内存能够进行分析和优化。
  • Dex文件有很多优化空间。在仔细统计并调整了Dex文件的顺序后,往往能够节约1M以上的mmap内存。
  • 引入SDK库和调用新的系统API时需要考虑成本。有可能一些不常用的功能会导致大量的消耗。这时候有可能需要多进程方案,将这些影响内存的操作放入临时进程执行。

1.6本章小结

在这一章里,我们通过对几个案例的分析,基本了解了Android应用的各种内存组成,以及这些成分是如何被消耗的,也总结出了一些节约和优化内存的经验。在这一小节里我们把经验都列出来供读者参考。

内存的主要组成索引

  • Native Heap:Native代码分配的内存,虚拟机和Android框架本身也会分配
  • Dalvik Heap:Java代码分配的对象
  • Dalvik Other:类的数据结构和索引
  • so mmap:Native代码和常量
  • dex mmap:Java代码和常量

内存工具

  • Android Studio/Memory Monitor:观察Dalvik内存
  • dumpsys meminfo:观察整体内存
  • smaps:观察整体内存的详细组成
  • Eclipse Memory Analyzer:详细分析Dalvik内存

测试经验

  • MAT是探索Java堆并发现问题的好帮手,能够迅速发现常见的图片和大数组等问题。
  • 仅靠MAT提供的功能也不是万能的,比如内存碎片问题就隐藏在对象的地址中。
  • 要测试非Dalvik部分,有必要了解Linux的进程和内存原理,内存共享机制,熟悉常用命令行工具。
  • 内存分配的最小单位是页面,通常为4K,这个限制往往会引发各种碎片问题。
  • 碎片不仅仅是Dalvik内存,包括各种文件的mmap也有可能产生碎片。

性能优化

  • 尽量不要在循环中创建很多临时变量。
  • 可以将大型的循环拆散,分段或者按需执行。
  • 引入SDK库和调用新的系统API时需要考虑成本。有可能一些不常用的功能会导致大量的消耗。这时候有可能需要多进程方案,将这些影响内存的操作放入临时进程执行。
  • 除了Dalvik堆内存,还有其他类型的内存在了解了原理后也能够进行分析和优化。
  • Dex文件有很多优化空间。在仔细统计并调整了Dex文件的顺序后,往往能够节约1M以上的mmap内存。

更多精彩内容欢迎关注腾讯优测的微信公众账号:

腾讯优测是专业的移动云测试平台,为应用、游戏、H5混合应用的研发团队提供产品质量检测与问题解决服务。不仅在线上平台提供app自动化测试、云真机远程操控与调试、私有自动化测试工具XTest等多种质量检测工具,更为VIP客户配备了专家团队提供定制化综合测试解决方案。

【腾讯优测干货分享】如何降低App的待机内存(五)——优化dex相关内存及本章总结的更多相关文章

  1. 【腾讯优测干货分享】如何降低App的待机内存(四)——进阶:内存原理

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/3FTPFvZRqyAQnU047kmWJQ 1.4进阶:内存原理 在 ...

  2. 【腾讯优测干货分享】如何降低App的待机内存(二)——规范测试流程及常见问题

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/806TiugiSJvFI7fH6eVA5w 作者:腾讯TMQ专项测 ...

  3. 【腾讯优测干货分享】越用越卡为哪般——如何降低App的待机内存(一)

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/1_FKMbi1enpcKMqto-o_FQ 作者:腾讯TMQ专项测试 ...

  4. 【腾讯优测干货分享】如何降低App的待机内存(三)——探索内存增长的原因

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/8BiKIt3frq9Yv9KV5FXlGw 1.3新问题的进一步挖 ...

  5. 【腾讯优测干货分享】安卓专项测试之GPU测试探索

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57c7ffdc0569a1191bce8a63 作者:章未哲——腾讯SNG质 ...

  6. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  7. 【腾讯优测干货分享】微信小程序之自动化亲密接触

    本文来自于腾讯优测公众号(wxutest),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/HcPakz5CV1SHnu-U8n85pw 导语 山雨欲来风满楼,最 ...

  8. 【腾讯优测干货分享】Android内存泄漏的简单检查与分析方法

    本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d14047603a5bf1242ad01b 导语 内存泄漏问题大约是An ...

  9. 【腾讯优测干货】看腾讯的技术大牛如何将Crash率从2.2%降至0.2%?

    小优有话说: App Crash就像地雷. 你怕它,想当它不存在.无异于让你的用户去探雷,一旦引爆,用户就没了. 你鼓起勇气去扫雷,它却神龙见首不见尾. 你告诫自己一定开发过程中减少crash,少埋点 ...

随机推荐

  1. DOS下删除整个目录及下属所有文件夹及文件最好用的命令

    Windows XP以上的版本,在使用DOS命令模式下删除目录(目录就是档案总管中所谓的资料夹)不是用 deltree,而是用 rmdir 指令. 在Windows XP.2000.NT下都可用rmd ...

  2. Windows获取其他进程中Edit控件的内容

    最近做的MFC项目中,有个获取其他进程中Edit控件内容的需求,本来以为是个很简单的问题,但是来来回回折腾了不少时间,发博记录一下. 刚开始拿到这个问题,很自然的就想到GetDlgItemText() ...

  3. 探索Microsoft.NET目录

    所在目录:D:\Windows\Microsoft.NET(d盘为系统盘) 文件目录 |--D:\Windows\Microsoft.NET |------assembly |------GAC_32 ...

  4. SHELL学习笔记----IF条件判断,判断条件

    SHELL学习笔记----IF条件判断,判断条件 前言: 无论什么编程语言都离不开条件判断.SHELL也不例外.  if list then           do something here   ...

  5. 谷歌笔试题——排序,只允许0和其他元素交换

    2.2 长度为n的数组乱序存放着0至n-1. 现在只能进行0与其他数的swap,请设计并实现排序. 这题有一个隐含条件:即数组元素是连续的,即0--n-1,当你排好序后,你会发现数组元素和该元素的下标 ...

  6. 织梦dede_archives文章主表详细介绍

    dede_archives文章主表,存放着各频道文章的主要信息,比如创建时间,所属栏目,所属频道,作者等详细的信息.     ID int(11) 自动编号typeid int(11) 所属主栏目编号 ...

  7. easyui源码翻译1.32--TimeSpinner(时间微调)

    前言 扩展自$.fn.spinner.defaults.使用$.fn.timespinner.defaults重写默认值对象.下载该插件翻译源码 时间微调组件的创建基于微调组件.它和数字微调类似,但是 ...

  8. 轻松使用Nginx搭建web服务器

    如果读者以前做过web开发的话,就应该知道如何去搭建一个web服务器来跑你的web站点,在windows下你可能会选择去用IIS,十分的快捷,在linux下,你可能首先会想到apache,“一哥”( ...

  9. win8.1中如何获得管理员权限步骤

    按WIN+R,运行对话框中输入gpedit.msc,开启组策略, 然后一步步地在"计算机配置"-"Windows 设置"-"安全设置"-&q ...

  10. Android 设置按钮背景透明与半透明_图片背景透明

    Button或者ImageButton的背景设为透明或者半透明 半透明<Button android:background="#e0000000" ... />  透明 ...