QAPM新内存分析"专家",帮你分析内存问题根因
一. 背景
QAPM原有Hprof分析是基于开源项目LeakCanary的shark Andoroid Extension,这里仅有针对安卓内存泄露部分,同时包含了一个极其简陋的内存触顶分析模块,只能根据一定规则获取极少的信息。为了适应更多针对内存触顶的新分析需求:如图片重复,图片超尺寸,字符串重复,对象重复分析与问题引用链聚类等更复杂的Hprof分析,包括获取更多问题信息时,原方案就显得力不从心,因此重构成了唯一的选择。
二. 原生方案的缺陷
原生的内存触顶方案的最致命缺陷在于:能够为技术人员提供的信息太少了,老版本的内存触顶所给出的个例中仅有简单的问题条目以及次数罗列,没有更多的信息出现。因此对技术人员来讲,这样的信息太过于"鸡肋",它只能告诉你发生了什么,却不回答为什么,有多严重,在哪里,以及有什么其他的具体信息。
三. 重构早期原型方案不足
原型方案在早期构建中使用了内存泄漏的shark库,当然也不是一帆风顺的,其中也陆续发现了一些不足之处,具体可以归结为以下几点
- 原生的堆对象代理体系索引较少,大部分操作使用Lazy Loading甚至为顺序查找,对一些要进行局部统计的操作极端不友好,耗时相对很长。
- 不保证读取线程安全,多个分析无法在一个索引上同时进行。
- 使用Okio + Position实现Lazy Loading,需要与IO进行交互,甚至在读取时都要创建一票对象,对GC造成压力,分析速度大大降低。
- 对象代理体系封装死板,类型系统不够简洁,没有从外部穿透的灵活性,进行复杂业务分析困难,改写也较为困难。
而这些劣势都会引导向两个主要的问题:
- 更新更复杂的需求难以实现
- 分析性能低下,体验不佳
四. 重构新方案的目标
因此,我们需要一个全新的Hprof分析组件,以支撑起我们的新需求开发,这个组件的架构主要改进并实现如下特点
- 大部分对象查找时间复杂度为O(1)
- 建立索引后必须保证无锁的并发
- 更简洁,高效,且优雅易用的代理对象体系
五. 新方案思路
1. 底层的改写
在具体实现思路上,我们在索引建立上使用了自己的一套体系,并且拥有全新的对象代理。
与shark不同,我们采用了较为激进的Eager Loading,对分析中常见的操作都建立了索引表,保证分析器查找取用数据的速度。这样一来也可以保证在索引建立完成后,所有的读取都是线程安全的,我们可以尽情的利用多核处理器的能力。
在另外一个层面上,根据业务的实际需求,我们针对代理对象的最短引用链获取做了特别的处理。即在分析时就将最短引用链求出,而不必像原有shark那样在用到时再进行计算。且在实际业务中要获取谁的引用链是无法预知的,这就造成了一个碰运气的问题:如果对象在BFS中遍历处于靠后的位置,或者是其根本从gc root不可达,再加之老方案遍历时是通过访问字段,而字段的加载又是极大可能要触发IO的。这样一整套组合拳下来,整个分析体验就会变得尤其糟糕。新Hprof通过牺牲一些内存,换取高速的引用链获取,极大地提高了体验。
改动前:
提单内容
旧内存分析 vs. 新内存分析 vs. LeakCanary 2
|
新内存分析 |
旧内存分析 |
LeakCanary 2 |
---|---|---|---|
分析项 |
多样化,根据分析器制订 |
少量分析,根据规则制定 |
仅有泄露 |
分析结果 |
针对不同类型有专门化的详细信息(如图片的尺寸,像素信息等) |
无 |
无 |
便利功能 |
拥有图片预览导出,字符串内容预览等便捷功能 |
无 |
少量 |
九. 后续规划
虽然目前已经取得了一些成果,但这还远远不够。
- 我们需要更多的分析器加入,如对于普通集合类型的低效利用(过短或者持有过多的空引用),引用值类型的分析(java.lang.Integer)等。
- 导出更多分析信息(例如针对Bitmap在不同Android版本的信息获取),来更好的定位内存中的问题所在。
- 美化信息的输出,提供更加易读,准确的结果。
- 考虑提供演进更优的框架设计,获取更好的性能来提升我们的分析体验。
- 考虑使用更适合模拟大量小对象的语言进行重写
如有兴趣或任何疑问,请联系在线客服:QAPM