动态内存泄露分析
时间:2017年9月29日 周五
1、内存泄露
通常说的内存泄漏是指堆内存的泄漏。
堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存。创建对象时,用alloc、new、copy等方法从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
如果程序运行时一直分配内存而不及时释放无用的内存,程序占用的内存越来越大,直到把系统分配给该APP的内存消耗殚尽,程序因无内存可用导致崩溃。
可能引起的问题:
1)内存消耗殆尽的时候,程序会因没有内存被杀死,即crash;
2)当内存快要用完的时候,会非常的卡顿;
3)如果是ViewController没有释放掉,引起的内存泄露,还会引起其他很多问题,尤其是和通知相关的。没有被释放掉的ViewController还能接收通知,还会执行相关的动作,所以会引起各种各样的异常情况的发生。
从iOS5开始apple采用ARC机制,不需要我们对内存进行管理了。但在实际开发过程中,不可避免的出现内存泄露的情况。
2、xcode自带的工具
Instrument中的Leaks、Allocations工具可以帮助我们进行内存泄露的排查,但它们存在不足。
在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限;
Instrument 的 Allocations 检测出 Abandoned memory,但是用起来不方便,需要一个一个地重现场景,且无法及时的知道泄露,比较繁琐。总之,一句话就是,不好使。
3、开源框架
在 GitHub 上有一些内存泄露检测相关的项目,例如 HeapInspector-for-iOS 和 MSLeakHunter。
1)HeapInspector-for-iOS
HeapInspector-for-iOS 可以说是 Allocations 的改进。它通过 hook 掉 alloc,dealloc,retain,release 等方法,来记录对象的生命周期。具体的检测内存泄露的方法和原理,与 Instrument 的 Allocations 一致。然而它跟 Allocations 一样,存在的问题是,你需要一个个场景去重复的操作,还有检测不及时。
2)MSLeakHunter
MSLeakHunter 就简单得多,它只检测 UIViewController 和 UIView,通过 hook 掉 UIViewController 的 -viewDidDisappear: 方法,并认为 -viewDidDisappear: 后,UIViewController 将很快被释放,如果 UIViewController 没有被释放,则打个建议日志。这种做法其实不是很好,-viewDidDisappear: 被调用可能是因为又 push 进来一个新的 ViewController,把当前的 ViewController 挡住了,所以可能有很多错误的建议,需要结合你实际的操作去具体地分析日志。
4、动态检测工具:MLeaksFinder
1)MLeaksFinder检测内存泄露原理
MLeaksFinder用于检测viewController、view是否存在内存泄露的情况。
当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。
具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
if ([MLeakedObjectProxy isAnyObjectLeakedAtPtrs:[self parentPtrs]]) {
return;
}
[MLeakedObjectProxy addLeakedObject:self];
}
这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若3秒后没被释放,就会中断言,并且调用[MLeakedObjectProxy addLeakedObject:self];该方法会获取内存泄露的信息,并弹框显示。
2)特点:
A、不入侵代码
直接拖进工程,配置设置即可使用。
使用了 AOP 技术,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法。(AOP技术,可以阅读”漫谈iOS AOP编程之路“)
B、可以建立例外
对于有些 ViewController,在被 pop 或 dismiss 后,不会被释放(比如单例),因此需要提供机制让开发者指定哪个对象不会被释放,这里可以通过重载上面的 -willDealloc 方法,直接 return NO 即可。
C、手动扩展:MLCheck()
MLeaksFinder目前只检测 ViewController 跟 View 对象。为此,MLeaksFinder 提供了一个手动扩展的机制,你可以从 UIViewController 跟 UIView 出发,去检测其它类型的对象的内存泄露。如下所示,我们可以检测 UIViewController 底下的 View Model:
- (BOOL)willDealloc {
if (![super willDealloc]) {
return NO;
}
MLCheck(self.viewModel);
return YES;
}
3)MLeaksFinder的增强:FBRetainCycleDetector
MLeaksFinder做到的是:告诉我们,viewController、view是否存在内存泄露。
MLeaksFinder不能告诉我们,当viewController、view发生内存泄露时,是在什么地方出现的,不能提供内存泄露的详细信息。
因此,我们接入了一个检测循环引用的框架FBRetainCycleDetector,它是Facebook的一个开源工具,专门用于检测对象的引用情况,并输出存在引用循环中的各对象和引用。
当MLeaksFinder检测到内存泄露时,将存在内存泄露的对象传参给FBRetainCycleDetector,FBRetainCycleDetector检测该对象的引用情况,并弹框显示检测结果。
在MLeaksFinder.h文件中有个开关控制,是否开启FBRetainCycleDetector
#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 1
5、下面用一个例子来介绍它们的使用
1)创建GGMLViewController,给它制造一个内存泄露:
2)运行工程
点击测试工程中的”MLeaksFinder使用“
弹出新的ViewController,一直点击按钮
action按钮即可,点到不能再弹出新的ViewController后,就点击back,回退到首页。
等待3秒时间,弹出循环引用的提示框
点击”定位“,弹出相关循环引用的信息,根据这信息,我们可以清楚的知道哪里出现了问题。
Demo地址:https://github.com/lignpeng/testDemo
注意:在使用MLeaksFinder时,建议把All Exceptions断点禁掉,避免出现些没必要的中断,这种中断是在FBRetainCycleDetector框架出现的,但不影响使用。
引用文章:
1)MLeaksFinder:精准iOS内存泄露工具: http://wereadteam.github.io/2016/02/22/MLeaksFinder/
2)使用FBRetainCycleDetector检测引用循环:http://www.jianshu.com/p/2c7a7c53c91a
3)漫谈iOS AOP编程之路:http://www.jianshu.com/p/addd4eac54ed
4)iOS内存泄露个人经验:http://www.jianshu.com/p/10254b4b19f3
5)iOS 测试工程Demo:http://www.jianshu.com/p/4580a370a4d3