新闻资讯  快讯  焦点  财经  政策  社会
互 联 网   电商  金融  数据  计算  技巧
生活百科  科技  职场  健康  法律  汽车
手机百科  知识  软件  修理  测评  微信
软件技术  应用  系统  图像  视频  经验
硬件技术  知识  技术  测评  选购  维修
网络技术  硬件  软件  设置  安全  技术
程序开发  语言  移动  数据  开源  百科
安全防护  资讯  黑客  木马  病毒  移动
站长技术  搜索  SEO  推广  媒体  移动
财经百科  股票  知识  理财  财务  金融
教育考试  育儿  小学  高考  考研  留学
您当前的位置:首页 > IT百科 > 程序开发 > 移动端 > Android

自动识别 Android 不合理的内存分配

时间:2019-09-10 10:54:32  来源:  作者:
自动识别 <a href=http://www.solves.com.cn/it/cxkf/ydd/Android/ target=_blank class=infotextkey>Android</a> 不合理的内存分配

 

写在前面

Android开发中我们常常会遇到不合理的内存分配导致的问题,或是频繁GC,或是OOM。 按照常规的套路我们需要打开Android Studio录制内存分配或者dump内存,然后人工分析,逐个排查问题所在。 这些方法是官方提供的能力,可以帮助我们排查问题,但难免有些繁琐,效率比较低。

如果可以自动识别出不合理的JAVA(含Kotlin)对象分配,这样繁琐的工作将会变得简单。

本文介绍了一种在Art虚拟机上实时记录对象分配的实现方案,基于此方案就可以实现不合理对象分配的自动化的识别。

常规方案对比分析

自动识别 Android 不合理的内存分配

 

Dump内存和字节码插桩的方案都无法覆盖运行过程中内存分配的过程,无法满足自动识别的诉求。 而录制的方案目前主要的问题是,不能自动化,如果能实现录制内存分配的自动化,就可以完成我们想要做的事情。

让录制对象分配自动化

1 . 模仿

Android Studio是 开源 的,因此我们很容易在它的源码里找到一些功能的实现。 录制内存分配的代码在ToggleAllocationTrackingAction这个类里。 精简后的流程如下:

自动识别 Android 不合理的内存分配

 

建立ADB连接、构造请求这些都是IDE做的事情,我们需要模拟IDE做这些事情吗? 不需要。 我们只需要关注DdmVmInternal是怎么做的即可,很幸运,Android系统源码的一段测试代码直接告诉了我们如何反射调用DdmVmInternal提供的能力,源码位置在<android src>/art/test/098-ddmc/src/Main.java,这里代码就不贴了。

2 . 转折

调用DdmVmInternal的方法,成功的在App里开启了内存分配的录制,也成功的拿到了每次内存分配的数据。 但如果以为事情就这样OK了,还早了一些。 万万没想到,这接口虽然易用,但用得并不爽,有三点:

  1. 最多只能65535条记录(size的类型是双字节无符号数)。
  2. 录制时对性能影响很小,但每次获取录制记录时特别慢(开发机实测JDWP封包5秒以上,解包处理10秒以上)。
  3. 每次获取到的记录可能有重复,要使用这个数据需要额外做合并去重的操作。

这些不爽的点似乎都很冗余,能不能直接一点呢?

3. 突破

DdmVmInternal的实现是放在native层的,顺藤摸瓜,我们找到了虚拟机里实现内存分配录制的源码,此处是Android5.1的源码,其他版本有差异,后面会讲到。

自动识别 Android 不合理的内存分配

 

这里的 关键函数是RecordAllocation ,所有对象的内存分配都会经过这个函数,因此我们可以Hook这个函数来捕捉到内存分配的事件。

 

怎么hook ?

自动识别 Android 不合理的内存分配

 

显然,PLT Hook并不适合我们的场景,好在目前Inline Hook技术也已经比较成熟,看雪有不少大佬都分享了自己的框架,我们要使用Inline Hook无需再处理那些繁琐的指令修复

关于hook技术的细节在最后的参考文章里有列举,有兴趣的同学可以翻阅 )。

至此,我们已经可以捕获到所有的对象分配事件了,但这只是我们迈出的一小步。

让对象分配可被跟踪

为了让对象分配可被跟踪,我们至少需要三个信息: 这是什么对象 ; 分配了多大内存 ; 它是怎么分配的 。 这几个点看似清楚明了,但怎么做,还需要小费一番周折。

1 . 分配了多大内存

这个信息最容易获取,如果你还记得RecordAllocation函数的定义,你会发现byte_count已经作为参数传进来了。 没错,就是这么简单。

2 . 这是什么对象

你也许已经发现RecordAllocation还有一个参数是art::mirror::Class*,这是Java里Class在虚拟机里的镜像,我们知道Java里拿到Class,就能直接调用getName方法知道这个类是什么。 然鹅,在虚拟机的源码里,GetName函数有是有,但是是内联函数,我们没有办法拿到这个函数的地址。

自动识别 Android 不合理的内存分配

 

这个咋整? 不要方,我们继续看源码,就在不远处,有一个叫个GetDescriptor的函数。

自动识别 Android 不合理的内存分配

 

可以说是业界良心了,我们通过dlsym就可以拿到这个函数的地址,然后调用它,传入我们已经拿到的art::mirror::Class*和一个std::string,就可以拿到类名(实际上是类的描述)。

3 . 它是怎么分配的

要知道一个对象是怎么分配的,我们需要拿到它的调用栈,Ok,我们来看看虚拟机里面怎么做的。

自动识别 Android 不合理的内存分配

 

这个能模仿实现吗? 多番查探,发现每个关键节点的实现都是