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

摘要:
图1-16在MAT中似乎不消耗内存的类。这些对象是各种类型的元数据。我们可以从代码逻辑分析执行这段代码需要分配多少内存,还可以看到MAT中新对象消耗的内存。从dex文件将类信息加载到内存中:2)分配本机堆和dalvik堆内存以创建类对象。创建类实例的每个步骤都需要消耗内存。接下来,让我们粗略计算新操作所消耗的内存。

本文来自于腾讯优测公众号(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所示:

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

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

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

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

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

  • 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内存的利用率

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

以上数据中可以看到,很大一部分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文件结构

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

简而言之,为了节约空间,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内存。

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

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

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

免责声明:文章转载自《【腾讯优测干货分享】如何降低App的待机内存(五)——优化dex相关内存及本章总结》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇鼠标显示形状图片,cursor:pointer手形状是最常用的显示方式,其实还有其他形式左式二叉堆下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android开机logo修改方法 【转】

本文转载自:http://blog.csdn.net/qq258711519/article/details/7766303 一体机平台开机logo修改方法 1:修改Kernel中的Logo:        若是要替换Kernel中的开机Logo,只需要把内核目录drivers/video/logo下的logo_android_1024_clut224.p...

angularJs学习笔记-入门

1.angularJs简介   angularJs是一个MV*的javascript框架(Model-View-Whatever,不管是MVVM还是MVC,统归MDV(model drive view)),其实是由google推出的SPA(single-page-application)应用框架。它的用于 数据在后端和前端之间的双向绑定。这就意味着你在后台...

关于hive当中表的存储和压缩方式总结

这几天研究hive表的存储方式和压缩模式。在这里做一个简单的总结 hive表的存储 样例 : 我的表:rp_person_house_loan_info             数据总量:1933776 textfile:   (1)hive数据表的默认格式,存储方式:行存储 。   (2) 可使用Gzip,Bzip2等压缩算法压缩,压缩后的文件不支持sp...

一分钟开始持续集成之旅系列之: Vue + 腾讯云 COS 上传部署

前言 随着Web应用的发展,运行在浏览器端的Web应用能够承担更多且更复杂的业务交互需求,前后端分离模式得以流行,并催生了如 Vue 、 React 等单页应用框架。这些框架简化了开发流程,但对于Web应用的部署并没有统一的解决方案。许多项目团队中,前端开发者在提交代码后仍然需要人工通知项目经理、运维等相关同事进行手动或者繁琐的更新操作。而将持续集成的概念...

数据库的触发器

一、触发器是一种特殊的存储过程,不能被显式调用,只能在对表进行insert、update、delete操作时被自动激活。所以触发器可以用来实现对表进行复杂的完整性约束。 二、 Sql Server为每个触发器都创建了两个专用表:Inserted表和Deleted表。这两个表由系统来维护,它们存在于内存中而不是数据库中。这两个表的结构总是与被该触发器作用的...

JAVA内存关注总结,作为个程序员需要对自己系统的每块内存做到了如指掌

 服务器的JAVA进程使用的内存是否正常  服务器中,JAVA进程的内存占用= JVM内存+ JAVA堆最大内存大小(Xmx)+JAVA堆外内存大小+栈区( 线程数* Xss) 最需要关注: 1., 服务器内存是否够JAVA进程开销 坑点: 每次JAVA的启动,只是检查当前linux的RES内存, 并不会检查申请的内存大小。 如, 服务器内存16G ,J...