译文:iOS端自动内存泄漏检测工具

产生的背景

在移动设备上内存是一块公用的区域,如果一个App没有做好内存管理那么一定会导致性能急剧下降甚至会崩溃。

Facebook的iOS端有许多的地方都共享着一块内存,如果任何一个地方占用太多的内存的话就会影响到整个App,比如一个地发生了内存泄漏,就会出现这种情况。

我们把一组内存分配我们的一个对象,但是当我们使用完之后忘记释放他,这就通常就会引起内存泄漏,这就意味着系统永远不能回收这块内存也就导致这块内存一直不能分配给别的对象。

在Facebook里我们有许多许多的工程师在代码的不同部分工作,内存泄漏时不可避免的,当一旦有内存泄漏发生我们就需要立即找到并且修复。

虽然现在有好多检测内存泄漏的工具但是这些工具并不完善,他们仍然需要开发者去做一些工作
    1:打开Xcode并且Build
    2:运行instrument
    3:使用App尽可能的去复现
    4:寻找内存泄漏的来源
    5:解决问题

这意味着需要大量的体力活,并且都是些重复无聊的工作,这也导致了我们不能在开发周期就定位并且修复问题。

将这个过程自动化可以让我们在不需要太多的开发者的情况下更快的去找到内存泄漏。为了解决这个问题我们已经建立一套工具使我们能够自动的去完成这些过程,并且这套工具已经解决了我们自己代码的一些问题,今天我们很激动的宣布我们把他们发布了出来FBRetainCycleDetector, FBAllocationTracker, and FBMemoryProfiler.

循环引用

Objective-C使用引用计数来管理内存的,内存中的一个对象可以引用其他的对象,只要有一个对象使用它,那么他就会一直被留在内存中。我们也可以说一个对象持有另一个对象。

  一般来说这都没问题。但是有一种情会使我们陷入僵局。当两个obj不在相互持有,而是通过另一个obj来互相持有,这样就会陷入一个循环引用的状态就像下面这张图
循环引用
循环引用

一个View Controller持有一个Viw View中持有delegate delegate中持有ViewController。这样就形成一个环状,谁也无法释放。

  循环引用会导致一些列的的问题,如果一个对象在RAM中无限的占用空间,充其量也只是浪费一点点内存。如果这些泄漏的对象正在做一些其他的事情那么就会导致App的其他的地方再也无法使用这块内存。更严重的如果循环引用过多,就会浪费掉大量的内存最终导致程序的crash。

  在我们进行人工调试的时候我们已经找出了大量的很明显的循环引用,我们能很好地定位他,但是在运行之后我们就很难发现他们,但是我们这SDK能很轻松的做这些事。

在Runtime下的循环引用检测

  在OC中找循环引用其实就类似于在一个节点为对象,链接线为引用关系的有向无环图中寻找一个环。打个比方如果A引用B那么A到B就会有箭头指向可以看这个


image.png

我们发现这张图在箭头处发生循环引用

  这个是一个很简单的引用关系我们必须确保我们能像左侧那样的来引用对象,其实对于每一个对象我们都获取他所有引用(持有的)对象,有的是强引用有的是弱引用,但是循环引用只发生在强引用上,对于每个对象我们需要搞清楚如何找到引用关系。

  幸运的是OC给我提供了强大的Runtime,这足够让我们去挖掘其中的关系,图中的一个点可能是block或者是Object中的其中一个,让我们来分别讨论下

Objects

  Runtime中有很多工具去帮助我们了解其中的东西,首先我们搞懂到所有实例变量的引用关系

const char *class_getIvarLayout(Class cls);
const char *class_getWeakIvarLayout(Class cls);

对于一个给定的对象,实例变量布局图告诉我们了他都引用了哪些对象,他会给我提供一个索引,这个索引相当于一个偏移量,该对象加上这个偏移量就是他所引用的对象的地址。运行时会给我们提供一个“弱引用”的布局图,也就是该对象所有弱引用的对象,强引用和弱引用之间的区别我们可以猜想为就是强引用的布局图。

  对于objective-c++来说我们可以用结构体来定义一个对象,这些对象不会再实例变量的布局图中被获取到,不过Runtime给我提供了“类型编码”来处理这个问题,对于每个实例变量,类型编码描述了变量是如何构造的。如果它是一个struct,类型编码可以描述出它包含的字段和类型。我们解析类型编码以找到哪些实例变量是objective-c的对象。然后我们可以像在布局图中那样计算他的偏移量然后拿到他所引用的对象的地址。

  还有一些我们不会深入讨论的边缘案例。这些都是不同的集合,我们必须列举它们来获取它们的保留对象,这可能会产生一些副作用。

Blocks

  block和对象有一点不同。运行时不允许我们轻松地查看它们的布局,但是我们仍然可以进行猜测。在处理block时,我们使用了Mike Ash在他的项目Circle中提出的想法:也正是这个项目激发FBRetainCycleDetector的项目。

 $emsp;我们可以使用application binary interface for blocks (ABI),他可以向我们展示一个block在内存中是什么样子的,如果我们知道了我们所研究的block的表现形式我们就可以用一个假的结构体来模仿实现block的功能,之后我们就能知道了哪些对象被block引用并且一直在那里,但是不幸的是我们并不知道这些事强引用还是弱引用。

  为了做到这些我们使用黑盒的方法,首先我们创建一个假的对象来模仿我们所研究的Block,因为是我们自己创建的假对象,所以我们可以完全的操作他,我们给这个假装的block上面装上释放探测器,释放探测器是监听发给他们的释放消息的一个对象,如果一个所有者想放弃对对象们所有权的时候,释放探测器会给他所有强引用的对象发出消息,当我们释放我们的假对象时,我们可以检查哪些探测器收到了这样的消息。便知道了哪些block是被强引用的。这样一来我们可以找到我们原始block所持有的真是的对象,如下图所示


block
block

自动化

  员工在进行持续执行时,这个工具就会非常出色。自动化在客户端上是非常容易的,我们使用定时器来建立一个循环引用检测,用来周期性的扫描一部分内存去寻找循环引用,不过还是有点问题,我们第一次运行检测器的时候我们发现他不快速的扫描完整个内存空间,所以我们需要给他提供一个候选检测对象,为了做到这一点,我们建立了FBAllocationTracker,他可以追踪任何一个Nsobject对象的创建和销毁,他同样可以在任何给定的时刻以最小的性能开销来获取任何类的实例对象。

  在客户端上实现了这样的自动化操作意味着我们可以更加简单的在NSTimer上使用FBRetainCycleDetector和添加用了追踪实例的FBAllocationTracker,现在让我们来看下后端到底发生了什么,循环引用可以有多个对象来组成,如果创建了许多的引用环并且有一个泄漏了那么事情将会变得非常复杂。如下如所示A->B就是一个坏的引用环导致了A->B->C->D和A->B->C->E
引用环
引用环

遇到这样的问题就会给我们的SDK带来两个问题:

  • 如果这两个环是由于一个不良引用引起我们就直接标记一处不良引用就可以了,不用标记两处,这样会给开发者一个错误信号。

  • 如果这两个环是虽然由一个不良引用引起但是会导致两个不同的错误,那么我们就需要将这个两个循环引用都标记出来

因此我们需要建立一个循环引用群,我们通过下面这些启发写了一种算法。

  • 1:把给定日期中所检测出的所有循环引用收集起来。
  • 2:找到每个循环引用环中Facebook特定的类名。
  • 3:找到每个环中最小的那个环。
  • 4:把最小周期放到一个组中。
  • 5:仅仅只像开发者报告最小的周期。

有了这些最后一部分就是找出谁可能会意外的引入一个循环引用,我们通过“git/hg blame”来做到这些。猜测这可能是导致出现问题的最新修改。最后一个提交代码的人会收到一个通知。整个系统可以如下展示
系统图
系统图

手工配置

  尽管自动化帮助简化了寻找循环引用的过程,并减少了开发人员的工作,但是手动配置仍然很重呀。我们开发的另一款工具允许任何人查看应用程序的内存使用情况,甚至不用将手机插入电脑。FBMemoryProfiler可以很容易地添加到任何应用中,让你手动配置你的工程,并在应用中运行循环引用检测,它可以通过使用FBAllocationTracker和fbretaincycle检测器来完成。

原文链接https://code.facebook.com/posts/583946315094347/automatic-memory-leak-detection-on-ios/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容