在iOS上自动检测内存泄露
手机设备的内存是一个共享资源。应用程序可能会不当的耗尽内存、崩溃,或者遭遇大幅度的性能降低。
Facebook iOS客户端有很多功能,并且它们共享同一块内存空间。如果任何特定的功能消耗过多的内存,就会影响到整个应用程序。这是可能发生的,比如,这个功能导致了内存泄露。
当我们分配了一块内存,并设置了对象之后,如果在使用完了之后忘记释放,这就会发生内存泄露。这意味着系统是无法回收内存并交予他人使用,这也最终意味着我们的内存将会逐渐耗尽。
在Facebook,我们有很多工程师在代码库的不同部分上工作。这不可避免的会发生内存泄露。当发生内存泄露之后,我们需要尽快找到并修复它们。
一些工具已经可以找到内存泄露,但是它们需要大量的人工干预:
- 打开Xcode,给性能分析(profiling)编译。
- 载入Instruments。
- 使用应用程序,尝试尽可能多的重现场景和行为。
- 查看内存和泄露。
- 追踪内存泄露的根源。
- 修复这个问题。
这意味着每次都需要重复大量的手动操作。为此,在我们的开发周期上,我们可能无法尽可能早的定位和修复内存泄露问题。
自动化可以在不需要更多开发者的情况下,更快的找到内存泄露。为了解决这个问题,我们做了一套工具来自动化的处理和修复我们代码库中的一些问题。今天,我们很兴奋的发布这些工具:FBRetainCycleDetector、FBAllocationTracker、FBMemoryProfiler。
循环引用(Retain cycles)
Objective-C 使用引用计数去管理内存和释放不使用的对象。内存中的任何一个对象都可以持有(retain
)其他的对象,只要前面的对象需要它,对象就会一直保持在内存中。查看这个的一个方法是这个对象持有其他的对象。
在大部分时间内,这都工作的很好,但当两个对象互相持有的时候,这就会陷入一个僵局。直接,或者更常见的,通过间接对象连接它们。这种持有引用的环我们叫做循环引用(Retain cycles)。
循环引用会导致一些列的问题。最好的情况下,对象只会在内存中占有一点点位置。如果这个被泄露的对象正积极地做个一些不平凡的事情,应用程序的其他部分就只会有更少的内存。最坏的情况下,如果泄露导致使用超出可用内存的容量,那么,应用程序会崩溃。
在手动性能分析期间,我们发现,我们往往有一些循环引用。我们很容易引起内存泄露,但是很难找到它们。循环引用检测器可以很容易的找到它们。
在运行时检测循环引用
在 Objective-C 中找循环引用类似于在一个有向无环图(directed acyclic graph)中找环, 而节点就是对象,边就是对象之间的引用(如果对象A持有对象B,那么,A到B之间就存在着引用)。我们的 Objective-C 对象已经在我们的图中,我们要做的就是用深度优先搜索遍历它。
http://7i7i81.com1.z0.glb.clouddn.com/blogvideo_memoryleak_1.mp4
这有点抽象,但效果很好。我们必须确保我们可以像节点一样使用对象,对于每个对象,我们都可以获取到它引用的所有对象。这些引用可能是weak
,也可能是strong
。只有强引用才会导致循环引用。对于每个对象来说,我们需要知道如何找出这些引用。
幸运的是,Objective-C提供了一个强有力的、内省的运行时库。这让我们在图中可以有足够的数据去挖掘。
图中的节点可以是对象,也可以是Block。让我们来分别讨论一下。
对象
运行时有很多工具允许我们对对象进行内省。
我们要做的第一件事是获取对象的实例变量的布局(ivar layout)。
const char *class_getIvarLayout(Class cls);
const char *class_getWeakIvarLayout(Class cls);
对于对象,实例变量的布局描述了我们在哪儿可以找到其他对象的引用。它会提供给我们一个索引(index),这代表我们需要在对象地址上添加一个偏移量(offset),就可以得到它所引用的对象的地址。运行时也允许我们获取“弱引用实例变量布局(weak ivar layout)”。
这也部分支持Objective-C++。在Objective-C++中,我们可以在结构体中定义对象,但是这不会在实例变量布局中获取到。运行时提供了“类型编码(type encoding)”来处理这个问题。对于每一个实例变量来说,类型编码描述了变量是如何结构化的。如果这是一个结构体,它会描述它包含了哪些字段和类型。我们计算出它们的偏移量,在图中,找出它们所指向的对象。
也有一些边缘条件我们不会深入。大部分是一些不同的集合,我们不得不列举它们去获得它们持有的对象,这可能会导致一些副作用。
Block
Block和对象有一点不一样。运行时不会让我们很轻易的看到它们的布局,但是我们仍然可以猜测。
在处理Block的时候,我们可以使用 Mike Ash 在他的项目Circle(第一时间启发FBRetainCycleDetector的项目)中提出的想法。
我们可以使用的是ABI(application binary interface for blocks - 应用程序二进制Block接口)。它描述了Block在内存中的样子。如果我们知道我们在处理的引用是一个Block,我们可以把它丢在一个假的结构体中来模仿Block。在放到一个C语言的结构体之后,我们可以知道Block所持有的对象。不幸的是,我们不知道这些引用是强引用还是弱引用。
为了解决这个问题,我们使用了一个黑盒技术。我们创建一个对象来假扮我们想要调查的Block。因为我们知道Block的接口,我们知道在哪可以找到Block持有的引用。我们伪造的对象将会拥有“释放检测(release detectors)”来代替这些引用。释放检测器是一些很小的对象,它们会观察发送给它们的释放消息。当持有者想要放弃它的持有的时候,这些消息会发送给强引用对象。当我们释放我们伪造的对象的时候,我们可以检测哪些检测器接收到了这些消息。只要知道哪些索引在伪造的对象的检测器中,我们就可以找到原来Block中实际持有的对象。
自动化
让这工具真正闪光的是,在工程师内部构建的时候,它会连续的、自动的运行。
客户端部分自动化是简单的。我们在定时器上运行循环引用检测器,定期扫描内存去寻找循环引用,虽然这不是完全没有问题。当我们第一次运行分析器的时候,我们意识到它不足以很快的扫描整个内存空间。当它开始检测的时候,我们需要给它提供一组候选对象。
为了更有效的解决这个问题,我们开发了FBAllocationTracker。这个工具会主动跟踪NSObject
子类的创建和释放。它可以以一个很小的性能开销来获取任何类的任何实例。
对于客户端的自动化,只要在NSTimer
上使用FBRetainCycleDetector,再用FBAllocationTracker来抓取实例来配合跟踪就行。
现在,让我们来仔细看看后台会发生什么。
循环引用可以包含任何数量的对象。一个坏的连接会导致很多环的时候,这就复杂了。
在环中,A→B是一个坏连接,创建了两个环:A-B-C-D 和 A-B-C-E。
这有两个问题:
- 我们不想给一个坏连接导致的两个循环引用分别标记。
- 我们不想给可能代表两个问题的两个循环引用一起标记,即使它们共享一个连接。
所以我们需要给循环引用定义簇组(clusters),鉴于这些启发,我们写了个算法来找到这些问题。
- 在给定的时间收集所有的环。
- 对于每一个环,提取Facebook特定的类名。
- 对于每一个环,找到包含在环内的被报告的最小的环。
- 依据上面的最小环,将环添加到组中。
- 只报告最小环。
最后一部分是找出谁第一时间偶然引入了循环引用。我们可以通过环中的”git/hg责任”的部分代码来猜测最近的变化所导致的问题。最后一个接触这个代码的人将会收到修复代码的任务。
整个系统如下:
手动性能分析
虽然自动化有助于简化发现循环引用的过程,降低人员的消耗,手动性能分析依然有它的用武之地。我们创建的另一个工具允许任何人查看内存使用,甚至不需要把他的手机插到电脑上。
FBMemoryProfiler可以很容易的添加到任何应用程序,可以让你手动配置构建文件,可以让你在应用程序内运行循环应用检测。它会借用FBAllocationTracker和FBRetainCycleDetector来实现此功能。
http://7i7i81.com1.z0.glb.clouddn.com/blogvideo_memoryleak_2.mp4
生成(Generations)
FBMemoryProfiler的一个很伟大的特性是“生成追踪(generation tracking)”,类似于苹果的Instruments的生成追踪。生成只是简单的在两次标记之间拍摄所有仍然活着的对象的快照。
使用FBMemoryProfiler的界面,我们可以标记生成,例如,分配三个对象。然后我们标记另一个生成,之后继续分配对象。第一个生成包含我们一开始的三个对象。如果任意一个对象被释放了,它会从我们第二个生成中移除。
当我们有一个重复的任务,我们认为可能会内存泄露的时候,生成追踪是很有用的,例如,导航View Controller的进出。在每次开始我们的任务的时候,我们标记一个生成,然后,对之后的每个生成进行调查。如果一个对象不应该活这么长时间,我们可以在FBMemoryProfiler界面清楚地看到。
Check Out
无论你的应用程序是大是小,功能是多是少,好的工程师都应有好的内存管理。在这些工具的帮助之下,我们可以更简单的找到并修复这些内存泄露,所以我们可以花费更少的时间去手动处理,这样就可以有更多的时间去编写更好的代码。我们也希望你可以发现它们是有用的。在Github上check out下来吧。FBRetainCycleDetector, FBAllocationTracker 和 FBMemoryProfiler。
备注
使用方式可以参考FBMemoryProfiler上的 Usage,或者也可以参考我的另外一篇博客:FBMemoryProfiler 基础教程。
在iOS上自动检测内存泄露的更多相关文章
- 根据Facebook内存的管理使用,浅谈在iOS上自动检测内存泄漏问题
分装库下载:https://github.com/facebook/FBMemoryProfiler FBMemoryProfiler类库使用教程:http://ifujun.com/fbmemory ...
- 在Windows中 , 如何用leakdiag “自动”检测内存泄露 (自动记录日志)
一.基本用法 在LeakDiag中选择aaa.exe 然后选择Windows Heap Allocator来跟踪heap的使用,按start开始,等一会按log,然后再stop 会在c:\leakdi ...
- 使用新版Android Studio检测内存泄露和性能
内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴. 怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的 ...
- 检测内存泄露:Instruments中的Leaks
前言 如果要检测内存泄露,我们会使用Xcode7自带的Instruments中的Leaks工具来检测. 现在的开发环境是ARC,所以很少会出现内存泄漏的情况. 不过我们一定要养好码代码的规范性. 例如 ...
- Qt应用中检测内存泄露——VLD
本文简要描述一下在Qt应用中使用VLD来检测内存泄露.本次测试环境:QtCreator2.3 + Qt4.7.4-vs2008 + VS2008 Express. 1.下载并安装:VLD-2.2: h ...
- 使用LeakCanary检测内存泄露 翻译 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Android Studio检测内存泄露和性能
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com 首先需要明白一个概念, 内存泄露就是指,本应该回收的内存,还驻留在内存中. 一般情况下,高密度的 ...
- Android DDMS检测内存泄露
Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...
- xcode怎样分析检测内存泄露(iOS)
虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在.所以了解原理很重要. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露, ...
随机推荐
- VirtualBox故障一例
早上的测试环境,估计是任务太重了吧,在点击VirtualBox的快速休眠后,就没有响应了,查看日志,内容都是: aComponent={Console} aText={The virtual mach ...
- Windows mysql 5.6 zip 安装 并创建用户赋予数据库权限
1.下载mysql 5.6 zip 首先下载mysql 5.6 zip (在官网(http://www.mysql.com/downloads/) 或者其他网站都行), 然后解压在自己的电脑上 D:\ ...
- 在ubuntu中启用ftp服务
Vsftpd vsftpd,ftp服务端,本文转自http://wiki.ubuntu.org.cn/Vsftpd 目录 [隐藏] 1 stand alone和super daemon 2 安装 3 ...
- hive 中出现struct 结构化的问题
如果你使用udf,udaf,udtf中的某一个并且查询日志中出现如下之类的struct错误 java.lang.RuntimeException: Error in configuring objec ...
- HW6.3
import java.util.Scanner; public class Solution { public static void main(String[] args) { Scanner i ...
- poj2152 Fire
好难啊,我弱爆了. 题解看陈启峰的论文... /** * Problem:POJ2152 * Author:Shun Yao * Time:2013.9.2 * Result:Accepted * M ...
- 编译vo-aacenc遇到的问题
sourceforge更新了vo-aacenc到0.1.3,就把自己的编码器也更新到最新.编译过程中无聊多测试了一下 发现一个小问题http://sourceforge.net/projects/op ...
- Java中的IP对象以及本地域名解析
本地域名解析操作步骤: 1.打开C:\WINDOWS\system32\drivers\etc目录 2.找到host文件,用记事本打开 3.添加“空间IP 域名” package WebProgra ...
- nyoj 6 喷水装置(一)
喷水装置(一) 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以 ...
- web开发工具类
1.日期工具类 import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { public sta ...