LeakCanary源码分析第三讲-HeapAnalyzerService详解

前面的两篇文章介绍了LeakCanary的核心架构RefWatcher的工作原理,其核心内容就是查找泄漏对象的最短引用路径,这些工作都是在HeapAnalyzerService中完成的。

HeapAnalyzer

HeapAnalyzerService通过调用HeapAnalyzer的checkForLeak方法来进一步分析内存,使用HAHA将RefWatcher传递过来hprof文件解析成Snapshot对象,其中调用的方法包括SnapshotFactory的parse和HprofIndexBuilder的fill方法。解析得到的SnapShot对象和我们用MAT分析内存得出的对象结构类似,其源码如下:

它构建了一颗对象引用关系树,我们可以在这颗树中查询各个Object的信息,包括Class信息、内存地址、持有的引用以及被持有引用的关系。

找出泄漏对象

接下来的任务就是从Snapshot中找到一条有效的到泄漏对象之间的引用路径。通过调用findLeakTrace方法从Snapshot中找到泄漏对象,代码如下:

为了能准确的找到泄漏对象,LeakCanary通过泄漏对象的WeakReference在Snapshot中定位它。因为,如果一个对象泄漏,一定可以在内存中找到这个对象的弱引用(这一切都是RefWatcher的功劳),再通过弱应用对象的reference就可以定位到泄漏对象。

寻找最短引用路径

找到泄漏对象之后,接下来就是找到一条到泄漏对象的最短引用路径,这个过程由findLeakTrace来完成,实际上寻找最短引用路径的逻辑是封装在PathsFromGCRootsComputerImpl类的getNextShortestPath和processCurrentReferrefs方法中,代码如下:

这个类将整个内存信息抽象为一个以GCRoot为根的树,由于之前已经定位了被泄漏对象在这颗树中的位置,为了找到到GCRoot的最短路径,PathsFromGCRootsComputerImpl采用类似广度优先的搜索策略,在getNextShortestPath中从被泄漏的对象开始,调用一次processCurrentReferrefs将持有它引用的节点(父节点),加入到一个FIFO队列中,然后依次再调用getNextShortestPath和processCurrentReferrefs来从FIFO中取节点及将这个节点的父节点再加入FIFO队列中,一层一层向上寻找,哪条路径最先到达GCRoot就表示它应该是一条最短路径。由于FIFO保存了查询信息,因此如果要找次最短路径只需要再调用一次getNextShortestPath触发下一次查找即可,其算法原理如下图所示。(配图来自百度手机助手团队——感谢) shortest_path

接下来调用buildLeakTrace构建查询结果,将最短路径转换成最后用于显示的LeakTrace对象,它包括了路径上各节点LeakTraceElement组成的链表,代表了泄漏对象的最短路径。 最后将结果封装成AnalysisResult对象,传递到DisplayLeakService进行处理。这个service主要的工作是将检查结果写入文件,以便直接看到内存泄露的分析结果,同时以Notification的方式通知用户检测到了一次内存泄漏。使用者还可以继承这个service类来并实现afterDefaultHandling来自定义对检查结果的处理,比如将结果上传刚到服务器等。

总结

历时三个星期的LeakCanary源码分析系列文章就此告一段落。也是第一次写源码分析的文章,这种分析类型的文章看起来非常的枯燥晦涩,如果读者你看到了文章的结尾,那么我相信你一定是一个有耐心的程序员。今年的工资一定会翻倍。

最后送上彩蛋一枚,输入支付宝红包口令,领红包去吧~如果你觉得博客对你有用,你可以关注右侧我的微信公众号(android-it),也可以将文章分享给你的朋友

hongbao_0117



  copyright@黑月神话,转载请注明出处:vjson.com

发表评论