动态内存泄露分析

动态内存泄露分析

时间: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容