自动监测内存泄漏

 

看到facebook的一套内存泄漏检测工具,感受不错,想要查看原文能够点击(http://t.cn/Rqi6Saz),后续在去分析相关的开源工具git

 

手机设备上的内存属于共享资源。应用不合理的使用它会致使内存耗尽,崩溃以及致使性能的大幅度下降。算法

 

Facebook的iOS客户端有许多特性,它们共享同一个内存空间,因此假如某个特定的特性消耗太多的内存,这会影响到整个应用,好比某个特性意外的出现内存泄漏。app

 

当咱们为一组对象分配内存,若是使用完没有释放相应的内存就会致使内存泄漏状况的发生,这意味着系统没法回收该内存来用于其它用途,最终致使内存耗尽。工具

 

在Facebook,许多工程师在不一样的代码仓库上工做,这不可避免会有内存泄漏的状况发生,当出现这种状况时,咱们须要快速的找到并修复它们。布局

 

已经有一些工具来辅助咱们找到内存泄漏,不过须要大量的人工干预:性能

 

  1. 打开Xcode,选择build for profiling.学习

  2. 载入Instruments工具ui

  3. 使用app, 尝试尽量多的重现场景和行为编码

  4. 查看instrument的leaks/memoryspa

  5. 查找内存泄漏的根源

  6. 修复问题

 

这意味着每次都须要大量的手动操做,致使咱们可能在开发周期内没法尽早的定位以及修复内存泄漏的问题。

 

若是该过程可以自动化,咱们就可以在太多开发者干预的状况下快速找到内存泄漏。为此咱们构建一系列的工具来自动化查找以及修复代码仓库中的一些问题,这些工具包括:FBRetainCycleDetector, FBAllocationTracker以及FBMemoryProfiler

 

Retain cycles(循环引用)

 

Objective-C使用引用计数来管理内存以及释放不使用的对象,任何一个对象能够持有(retain)其它对象,这样只要前面的对象须要使用它,该对象就会一直保存在内存,能够认为对象“拥有”其它对象。

 

大部分状况下这都工做的很好,可是假如两个对象最后互相“拥有”对方,直接或着更多经过其它对象间接的链接它们,这就会陷入一个僵局。这种持有引用的环就叫作循环引用。

循环引用会致使一系列的问题,最优的状况是对象一直在RAM中只占据一点点的空间。若是泄漏的对象只是作一些可有可无的工做,那结果只是应用会少一些可以使用的内存。最差的状况下,假如泄漏的内存超过了可以使用的内存空间,应用可能会崩溃。

 

在手动性能分析的过程当中,发现咱们每每会有不少循环引用的状况,在开发的时候很容易出现循环引用,但却不容易在后面找到。Retain Cycle Detector能够帮助咱们很容易的找到它们。

 

运行时检测循环引用

 

在Objective-C中查找循环引用相似于在一个有向无环图(directed acyclic graph)中查找环,节点就是对象,而边则是对象之间的引用(若是对象A retain 对象B,那么A到B之间就存在引用)。咱们的Objective-C对象已经存在于咱们的图当中,咱们要作的就是使用深度优先方法历遍搜索它。(看到算法的重要性了。。)

 

虽然这只是个简单的抽象,但实际效果却不错。咱们必须确保咱们可以像节点同样使用对象,对于每一个对象,咱们可以获取它所引用的全部对象,这些引用多是weak或者strong,不过只有strong的引用才会致使循环引用。所以对于每一个对象,咱们须要知道如何找出这些强引用。幸运的是,Objective-C提供了一套强有力、内省的运行库,可以提供咱们足够的数据去挖掘这张图。

 

图中的节点能够是一个对象或一个block,让咱们分别讨论。

 

对象(Objects)

 

运行时有许多工具可以让咱们对对象进行内省学习,咱们要作的第一件事就是获取对象全部实例变量的布局(ivar layout)

 

//runtime.h

const char *class_getIvarLayout(Class cls);

const char *class_getWeakIvarLayout(Class cls);

 

对于一个给定的对象,实例变量布局描述了咱们该去哪查找其所引用的其它对象。它会提供一个“索引”,这个索引表明着偏移量(offset),咱们在对象地址上加上该偏移量来获取它所引用对象的地址。运行时还容许咱们获取“弱引用实例变量的布局(weak ivar layout)”,咱们能够认为这两种布局的差异在于强引用布局。

 

这也部分支持Objective-C++。在Objective-C++中,咱们能够在结构体中定义对象,但这不会在实例变量布局(ivar layout)中获取到,运行时提供“类型编码(type encoding)”来解决这个问题。对于每一个实例变量,类型编码描述了变量如何结构化的。若是变量是一个结构体,它描述了变量包含的字段和类型。咱们经过解析类型编码来找出哪些实例变量是Objective-C对象。咱们计算出它们的偏移量(offset),而后在布局中找到它们指向对象的地址。

 

有些边缘状况咱们不会深刻。大部分是一些不一样的集合,咱们须要历遍它们来获取它们持有的对象,这可能会有一些反作用。

 

Blocks

 

Blocks跟对象有些区别。运行时没有让咱们很容易看到它们的布局,但咱们仍然能够猜想。在处理Blocks的时候,咱们采用Mike Ash在他的Circle项目中的思路。

 

咱们可使用的是ABI(application binary interface for blocks),它描述了block在内存中的样子。若是咱们知道在处理的引用是一个block,那咱们可使用一个假的结构体来模拟该block对象。将block转换成一个C结构体后,咱们就能够知道block持有哪些对象,不过不幸的是,咱们不知道这些引用是强引用仍是弱引用。

 

为了解决这个问题,咱们使用一个黑盒技术,咱们建立一个伪造对象伪装是咱们要研究的block。由于咱们知道block的接口,咱们知道在哪能够找到block持有的引用,伪造的对象使用”release detectors”来替代这些引用。release detectors是一些小的对象,它们会观察发送给他们release的消息。当持有者想要放弃对象的拥有权时,release消息就会发送给它所强引用的对象。当咱们释放该伪造的对象后,能够检查哪些detectors收到了release消息。知道了接收release消息的detectors的索引位置以后,咱们就能够找到block对象所持有的强引用对象。

 

自动化

 

这些工具在工程师内部构建的时候可以持续自动的运行,确实闪闪发光。

 

在客户端的自动化很简单。咱们定时的运行Retain Cycle Detector,按期的去扫描内存查找循环引用,不过这并非这么一路顺风。咱们第一次运行Detector时,咱们意识到它没法很快的扫描整个内存空间,咱们须要首先提供一组候选对象来让Detector检测。

 

为了更有效的处理上述问题,咱们建立了FBAllocationTracker。这个工具可以记录全部NSObject子类对象的建立和销毁,它可以以极小的性能代价在任意时刻快速获取任何类的对象实例。

 

客户端有了上述的自动化过程,意味着咱们只须要在NSTimer上运行FBRetainCycleDetector,在配合FBAllocationTracker来抓取咱们想要分析的实例便可。

 

如今让咱们深刻的看一下背后具体发生了什么。

 

循环引用能够包含任意数量的对象,当因为一个坏的链接(bad link)致使不少环的产生,事情就变的更复杂了。

 

在上面的环中,A->B就是一个坏的链接(bad link),它建立了两个环:A-B-C-D和A-B-C-E。这会有两个问题:

 

  • 若是由同一个坏的链接致使两个循环引用,咱们不想用不一样的标记来分别标记它们;

  • 咱们不想给可能表明两个不一样问题的两个循环引用一块儿标记,即使他们共享一条链接。

 

因此咱们须要给循环引用定义类簇(clusters)。咱们写了一个算法来找出这些问题,算法以下

 

1. 在给定的时间,收集全部的环;

2. 对于每一个环,提取Facebook特定的类名称;

3. 对于每一个环,找出该环包含的最小环;

4. 将每一个环添加到由上面找到的最小环所表明的组中;

5. 只报告最小环;

 

最后要作的就是找到谁第一时间偶然地引入了循环引用,能够经过对环所涉及的代码进行’git/hg blame’,咱们猜想可能最新的代码致使了该问题,因此最后一个接触该代码的人会收到一个task来修复该问题。

 

整个过程以下图所示:

 

手动性能分析

 

虽然自动化能简化发现循环引用的过程,减小开发人员的消耗,但手动性能分析仍是必不可少。咱们建立了另一个工具,容许任何人查看内存的使用状况,甚至不须要把手机插到电脑上。

 

FBMemoryProfiler能够很容易的添加到任意应用,让你在应用内部手动配置构建文件以及运行循环引用检测,该工具借助FBAllocationTracker和FBRetainCycleDetector实现该功能。

 

代(Generations)

 

FBMemoryProfiler一个最大的特性是提供”代追踪(generation tracking)”,相似苹果instruments的generation tracking,Generations是两个时间标记之间全部仍然活着的对象的快照。

 

使用FBMemoryProfiler的界面,咱们能够标记一个generation,好比建立了三个对象;而后咱们标记另外一个generation并继续建立对象。第一个generation包含了咱们三个最初的对象,若是有对象被释放了,那么该对象就会在第二个generation中被移除。

 

假若有一个重复的任务,咱们认为可能有内存泄漏的状况发生,这时候Generation tracking就颇有效了。好比导航进入一个View Controller而后退出,每次开始任务以前,咱们标记一个generation,而后在每次generation标记之间进行调查,若有对象并不该该存活那么长,那咱们能够在FBMemoryProfiler的界面上清楚的看到。

相关文章
相关标签/搜索