FBMemoryProfiler

应用开发优化方面,内存占用是一个不可避免的话题。在开发过程中,内存泄漏的检测,一直是工作中不可避免的任务。
当我们分配了一块内存,并且设置对象。如果使用完成后忘记释放,就会发生内存泄漏,当然ARC之后,苹果已经自动帮做了很多内存的释放工作,但是如果开发过程不注意,有循环引用的话,还是会发生内存泄漏,当发生内存泄漏时,我们需要尽快找到并修复它们。
Xcode提供了Instruments工具来帮我们进行内存泄漏的检测,以下是步骤

  1. 打开Xcode,profiling编译。
  2. 载入Instruments。
  3. 使用应用程序,尝试尽可能多的重现场景和行为。
  4. 查看内存泄漏的根源。
  5. 定位后进行修复。

开发时,每次进行profile是很耗费时间的,同时重复大量的手动操作。
如果有一套自动化的工具,能够更快更方便的找到内存泄漏的话,开发效率也会大大提升。接下来就引入到了FB发布的三个工具:FBRetainCycleDetector、FBAlloctionTracker、FBMemoryProfiler.

循环引用 (Retain cycles)

Objective-C是使用引用计数来管理内存和释放不使用的对象。内存中任何一个对象都可以持有(retain)其他的对象,只要前面的对象需要它,对象就会一直保持在内存中,大部分时间内,这都工作的很好,但是当两个对象互相持有的时候,这就会陷入一个僵局,直接或者通过间接对象连接它们。这种持有引用的环,我们称之为循环引用(Retain cycles).


图示

循环引用会导致很多问题,最好的情况下,对象只会在内存中占有一点点位置,如果这个被泄露的对象还在进行一些工作的话,引用程序的其他部分可以使用的内存就会变少;最坏的情况下,如果泄漏导致使用超出可用内存的容量,那么,应用程序就会崩溃。
在手动性能分析期间,往往有会出现一些循环引用,很容易引起内存泄漏,但是进行定位的话,比较麻烦。尤其项目比较庞大且业务关联较多的时候。而循环引用检测器(FBRetainCycleDetector)可以很容易的找到他们。

在运行时检测循环引用

在Objectice-C中找循环引用类似于在一个有向无环图中找环,对象就是图的节点,边就是对象之间的引用(如果对象A持有对象B,那么,A到B之间就存在着引用)。我们的Objective-C对象已经在图中,我们要做的就是用深度优先搜索遍历它。以下是FB的示例:


示例

这个有点抽象,但是效果很好。我们必须确保我可以向节点一样使用对象,对于每个对象,我们都可以获取到它引用的所有对象。这些引用可能是weak,也可能是strong。只有强引用才会导致循环引用。对于每个对象来说,我们需要知道该如何找出这些引用。
幸运的是,OC为我们提供了一套强有力的、内省的运行时库。这让我们在图中可以有足够的数据去挖掘可能出现的环。
图中的节点可以是对象,同时block也是循环引用的高发地,我们分别看一下。

对象

运行时有很多工具允许我们对对象进行内省。
我们要做的第一件事就是获取对象的实例变量的布局(ivar layout).

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

对于对象,实例变量的布局描述了我们在哪可以找到其他对象的引用。它提供了我们一个索引(index),这代表我们需要在对象地址上添加一个偏移量(offset),就可以得到它所引用的对象的地址。运行时也允许我们获取"弱引用实例变量布局(weak ivar layout)"。
这也部分支持Objective-C++。在Objective-C++中,我们可以在结构体中定义对象,但是这不会在实例变量布局中获取到。运行时提供了"类型编码"来处理这个问题。对于每一个实例变量来说,类型编码描述了变量是如何结构化的。如果这是一个结构体,它会描述它包含了哪些字段和类型。我么你计算出它的偏移量,在图中,找到它们所指向的对象。稍后再看源码是会具体分析如何找出

Block

Block和对象有一点不一样。运行时不会让我们很轻易的看到它们的布局,但是我们仍然可以进行猜测。
我们可以使用ABI(application binary interface for blocks - 应用程序二进制Block接口)。它描述了Block在内存中的样子。如果我们知道我们在处理的的引用是一个Block,我们可以把它丢在一个假的结构体中来模仿Block。在放到一个C语言的结构体之后,我们可以知道Block所持有的对象。然而,有一个尴尬的问题,不知道这些引用的强引用还是弱引用。
为了解决这个问题,FB使用了一个黑盒技术。创建一个对象来假扮想要调查的Block。因为我们知道Block 的接口,我们知道在哪可以找到Block持有的引用。伪造的对象会拥有"释放检测(release detectors)"来代替这些引用。释放检测器是一些很小的对象,它们会观察发送给它们的释放消息。但持有者想要放弃它的持有的时候,这些消息会发送给强引用对象,但我们释放我们伪造的对象的时候,我们可以检测哪些检测器接收到了这些消息。只要知道那些索引在伪造的对象的检测器中,我们就可以找到原来Block中实际持有的对象。


示例
自动化

然而原理介绍完,真正提高检测效率的是,它可以连续的自动的运行。
客户端部分自动化是很简单的,我们可以在定时器上运行循环引用检测器,定期扫描内存去寻找循环引用,虽然这不是完全没有问题。但是运行的时候发现,扫描完整个内存空间比较慢。为了提高效率,我们可以在开始检测的时候,提供一组候选对象。FBAllocationTracker这个工具可以主动跟踪NSObject子类的创建和释放。它可以以很小的性能开销来获取任何类的任何实例。
现在呢,我们只要在NSTimer上使用FBRetainCycleDetector,再用FBAllocationTracker来抓取实例配合跟踪就行。
然而,实际过程中还是会遇到问题,循环引用可以包含任意数量的对象,一个错误引用会导致很多环的情况,处理起来比较复杂。如图


示例

在环中,A-B是一个坏连接,创建了两个环:A-B-C-D和A-B-C-E。
这种情况导致两个问题:

  1. 我们不想给一个坏连接导致的两个循环引用分别标记。
  2. 我们不想给可能代表两个问题的两个循环引用一起标记,即使它们共享一个连接。
    所以我们需要给循环引用定义簇组(clusters),鉴于这样的情况,FB写了一个算法来找到这些问题。
  3. 在给定的时间收集所有的环
  4. 对于每一个环,提取Facebook特定的类名。
  5. 对于每一个环,找到包含在环内的被报告的最小的环。
  6. 依据上面的最小环,将环添加到组中。
  7. 只报告最小环。
    最后一部分当然是找出导致循环引用的代码是谁写的,进行一番批评教育😂.
    这个系统如下:


    示例
手动性能分析

虽然自动化有助于简化发现循环引用的过程,降低人员的消耗,手动性能分析仍然有它的用武之地。FB创建的另一个工具允许任何人查看内存使用,即实时可视化。
FBMemoryProfiler可以很容易的添加到任何应用程序中,可以让你手动配置构建文件,可以让你在引用程序内运行循环应用检测。当然它是借用FBAllocationTracker和FBRetainCycleDetector来实现此功能。


示例
生成(Generations)

FBMemoryProfiler的一个很伟大的特性就是"生成追踪(generation tracking)",类似于苹果的Instruments的生成追踪。生成只是简单的在两次标记之间拍摄所有仍然存货的对象的快照。
使用FBMemoryProfiler的界面,我们可以标记生成,例如,分配三个对象。然后我们标记另一个生成,之后继续分配对象。第一个生成包含我们一开始的三个对象。如果任意一个对象释放了,它会从我们第二个生成中移除。


示例

当我们有一个重复的任务,我们认为可能会内存泄漏的时候,生成追踪是很有用的,例如,导航View Controller的进出。在每次开始我们的任务的时候,我们标记一个生成,然后对之后的每个生成进行调查。如果一个对象不应该存活这么长时间,我们可以在FBMemoryProfiler界面清楚的看到。
综上所述,无论开发的应用程序是大是小,功能是多是少,好的工程师都应该有好的内存管理。在这些工具的帮助下,我们可以更简单的找到并修复这些内存泄漏,可以有更多的时间去做更有意义的事,like编写更好的代码。

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

推荐阅读更多精彩内容