文章目录

  • 1、内存分类
  • 2、Memory Report
  • 3、Analyze
  • 4、Leaks
    • 4.1、前置设置
    • 4.2、页面介绍
    • 4.3、使用
    • 4.3.1、Leaks 页面
    • 4.3.2、Cycles & Roots页面
    • 4.3.3、Call Tree页面
  • 5、Memory Graph
    • 5.1、前置设置
    • 5.2、入口:
    • 5.3、使用分析:
    • 5.3.1、分析方式1:
    • 5.3.2、分析方式2:
  • 8、FBRetainCycleDetector
  • 9、RaftKit
  • 10、MLeaksFinder
    • 10.1、使用:
    • 10.2、分析 alert:
      • 10.2.1、单例 or 被 cache 起来的对象
      • 10.2.2、释放不及时
      • 10.2.3、真正的泄露
    • 10.3、查找循环引用链:
    • 10.4、原理
    • 10.4、扩展:
  • 11、泄露总结:
    • 11.1、Block
    • 11.2、NSTimer
    • 11.3、malloc -> free
    • 11.4、CFBridgingRetain - CFBridgingRelease
    • 11.5、被static持有了
    • 11.6、单例滥用
  • 12、工具总结:
  • 参考:

1、内存分类

官方文档介绍 app 的内存分三类:

Leaked memory:Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument)
Abandoned memory:Memory still referenced by your application that has no useful purpose
Cached memory:Memory still referenced by your application that might be used again for better performance

  • Leaked memory:app 没有引用的内存,无法再次使用或释放(可以使用 Leaks 工具检测)
  • Abandoned memory:app 仍有引用,但没有任何用途的内存
  • Cached memory:app 仍有引用,可能会再次使用以获得更好的性能

Leaked memoryAbandoned memory 都是应该释放而没释放的内存,属于内存泄露。

Leaked memory 可以用 InstrumentLeaks 检测出来。Leaks的实现思路是搜索所有可能包含指向 malloc 内存块指针的内存区域,比如全局数据内存块,寄存器和所有的栈。如果 malloc 内存块的地址被直接或者间接引用,则是 reachable 的,反之则是 leaks

Abandoned memory可以用 InstrumentAllocations 检测出来。检测方法是用 Mark Generation 的方式,当每次点击 Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息.


2、Memory Report

Xcode 运行项目时,切换到 Debug navigator 点击 memory 就可以查看 Memory Report,显示 内存使用 的整体情况:

用于定位内存泄露的话用处不大,只能看到内存的概况。


3、Analyze

静态分析入口:

分析案例:
缺陷:只能检查编译时的内存泄漏,并不能检测到所有的内存泄漏,如:发生在运行时,或需要用户操作时产生的泄露。


4、Leaks

4.1、前置设置

首先,修改编译设置生成符号信息,以便 Leaks 分析出调用堆栈函数符号:
Target -> Build Settings -> Build Options -> Debug Information Format -> Debug -> DWAPR with dSYM File

否则 Leaks 无法解析调用堆栈函数名:
no stack trace is available for this leak; it may have been allocated before the Allocation instrument was attached

用 Xcode 把 app 跑起来。打开Leaks:

入口在菜单栏:Xcode -> Open Developer Tool -> Instruments -> 然后选择 Leaks -> Choose (打开操作面板)

4.2、页面介绍

步骤1:选好设备和需要测试的 app
步骤2:点击同行最左边的红色按钮,开始录制(点击开始录制会重启 app)

录制过程中:

  • 左边按钮是停止,右边按钮是暂停:

  • 右侧会出现3种标志:
    绿色:没有发现泄露
    红色:发现新的泄露
    灰色:没有发现新的泄露

4.3、使用

4.3.1、Leaks 页面

默认选择的是 Lesks 页面,下半部分显示的是泄露的详情,左边是目前为止检测到的所有泄露;选中其中一个,右侧显示的是泄露点的调用堆栈,可据此找到泄露点进行修改。

底部栏:

  • snapshots,可以设置检测泄露的时间间隔,也有立即检测按钮:

  • Input Filter可通过线程过滤

  • Detail Filter可通过关键字过滤

也可选择时间段过滤:在起始时间点按下鼠标左键,拖动到截止时间点松开:

4.3.2、Cycles & Roots页面

点击中间栏的左侧切换到Cycles & Roots页面,可查看泄露图:

看图分析应该是因为block导致的循环引用,按调用堆栈找到对应的代码:

4.3.3、Call Tree页面

点击中间栏的左侧切换到Call Tree统计模式,也可通过底部栏的工具进行过滤
Separate By Thread:线程分离,在调用路径中能够清晰看到占用内存最大的线程
Invert Call Tree:反转调用堆栈顺序
Hide System Libraries:隐藏系统库的调用堆栈信息
Flatten Recursion:会将调用栈里递归函数作为一个入口(很少使用)

底部栏可设置各种约束进行过滤(用的比较少):
按符号过滤 or 按库过滤

设置最大最小值进行过滤:

设置 符号/库 变化时/删减掉 进行过滤:


5、Memory Graph

可显示当前所有 已使用内存 的详情

5.1、前置设置

Malloc Scribble:开启将使用预定义的值填充释放的内存,从而在内存泄漏时更加明显。这提高了Xcode识别泄漏的准确性。
Malloc Stack Logging:启用此选项将允许Xcode构建分配回溯,以帮助了解对象从何处引用。

5.2、入口:

Xcode 运行项目时可点击中部栏的Debug Memory Graph按钮,查看内存图:

5.3、使用分析:

5.3.1、分析方式1:

点击左侧 导航栏 - 底部栏 的 Show only leaked allocations 按钮,可过滤出泄露的对象:

例如:动画用到的 CGPath 没有释放:

5.3.2、分析方式2:

退出页面后点击 Debug Memory Graph,在底部Filter栏输入 关键字 过滤出当前还存活的对象,进行分析:

例如:退出直播间应该释放的插件没有释放:

以上介绍的都是 Xcode 自带的可视化工具,下面介绍的是其他代码检测工具。


8、FBRetainCycleDetector

Facebook 开源的 循环引用检测 工具 FBRetainCycleDetector
当确认或怀疑一个对象是否泄露时,都可以使用该工具查找循环引用链。

1). main 里添加对 objc_setAssociatedObject 的查找:

#import <FBRetainCycleDetector/FBAssociationManager.h>int main(int argc, char * argv[]) {@autoreleasepool {[FBAssociationManager hook];return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
}

2). 泄露后查找引用环:

#import <FBRetainCycleDetector/FBRetainCycleDetector.h>FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"retain cycle: %@ %@", [self class], retainCycles);

输出,例如:

("-> MyTableViewCell ","-> _callback -> __NSMallocBlock__ "
)

表示:cell 持有 block,block 持有 cell


9、RaftKit

腾讯视频已集成的 RaftKit (未开源)里的有 内存泄露监控 工具(底层用的是Bugly):

打开开关和提示弹框:

打开后,当发现泄露会弹出alert:

打开 RaftKit 在内存泄露工具里,查看内存泄露记录文件:

点击需要分析的泄露对象,查看详情:

内部也是使用FBRetainCycleDetector进行引用循环链的查找:

也可将文件导出:FloatingWebVC.txt
分析详情中的循环引用链:左边是实例名,右边实例的类型;从第一个到最后一个形成了一个引用环。
找到对应的类进行分析:

QNBUALiveShowLayoutBridgeBase 是持有 jsBridge 的,且 jsBridge 又间接持有该 block,所以在 block 里直接使用 self 就形成了引用环了。
(26个Handler,95% block 的写法都导致了循环引用)

没有引用环的,可以打开 Memory graph 分析被谁持有的。


10、MLeaksFinder

Tencent 的开源检测内存泄露库:MLeaksFinder
可在日常开放中默认打开,以便及时获得泄露警告,而不用特意打开以上工具去排查。

10.1、使用:

podfile里添加导入,然后执行 pod install

pod 'MLeaksFinder'
pod 'FBRetainCycleDetector'

使用 MLeaksFinder.h 的宏 MEMORY_LEAKS_FINDER_ENABLED 控制该工具是否可用.

MLeaksFinder 发现内存泄露时会弹出 Memory Leak 的 alert :

Memory Leak
(MyTableViewController,UITableView,UITableViewWrapperView,MyTableViewCell
)

表示:MyTableViewController,UITableView,UITableViewWrapperView 都已成功释放,但其 subView MyTableViewCell 没有释放。

并会持续追踪该对象的生命周期,并在该对象释放时给出 Object Deallocated 的 alert :

Object Deallocated
(MyTableViewController,UITableView,UITableViewWrapperView,MyTableViewCell
)

10.2、分析 alert:

10.2.1、单例 or 被 cache 起来的对象

如下所示,在第一次 pop 时报了 Memory Leak,在之后重复 push 并 pop 同一个 ViewController 过程中,即不报 Object Deallocted,也不报 Memory Leak。这种情况可以确定该对象是被设计成单例 or 被 cache 起来了。

    pop             push           pop           push          pop
----------> Leak ----------> | ----------> | ----------> | ---------->

10.2.2、释放不及时

如下所示,在第一次 pop 时报 Memory Leak,在之后的重复 push 和 pop 同一个 ViewController 过程中,对于同一个类不断地报 Object DeallocatedMemory Leak。这种情况属于释放不及时。

    pop             push                 pop             push                 pop
----------> Leak ----------> Dealloc ----------> Leak ----------> Dealloc ----------> Leak

10.2.3、真正的泄露

如下所示,在第一次 pop 时报 Memory Leak,在之后的重复 push 和 pop 同一个 ViewController 过程中,不报 Object Deallocated,但每次 pop 之后又报 Memory Leak。这种每次进入并退出一个页面后都报内存泄露,且被报泄露对象又从来没有释放过,可以确定是真正的内存泄露。

    pop             push           pop             push           pop
----------> Leak ----------> | ----------> Leak ----------> | ----------> Leak

10.3、查找循环引用链:

MLeaksFinder里也用了FBRetainCycleDetector来找找循环引用链:
MEMORY_LEAKS_FINDER_ENABLED控制是否启用FBRetainCycleDetector查找循环引用链;
_INTERNAL_MLF_RC_ENABLED设置alert弹框是否显示Retain Cycle按钮;

也可以打开 Memory graph 分析被谁持有的。

10.4、原理

NSObject新增一个-willDealloc方法:在 2s 后给弱引用的self发送assertNotDealloc消息:
self被释放则不会执行;
self未被释放则会执行assertNotDeall

然后在UIViewControllerdismiss方法里调用willDealloc:遍历 childVCspresentVCssubViews触发他们的willDealloc方法检测是否有泄露:

10.4、扩展:

MLeaksFinder 目前只检测 ViewController 跟 View 对象。为此,MLeaksFinder 提供了一个手动扩展的机制,开发者可以从 UIViewController 跟 UIView 出发,去检测其它类型的对象的内存泄露。如下所示,可以检测 UIViewController 持有的 View Model:

- (BOOL)willDealloc {if (![super willDealloc]) {return NO;}MLCheck(self.viewModel);return YES;
}

11、泄露总结:

通过排查腾讯视频直播间的整体泄露后,发现泄露类型基本都是以下5类:

11.1、Block

Block 会强引用捕获到的对象,如果该对象 直接 或 间接 强引用该 Block,则会导致循环引用:

11.2、NSTimer

NSTimer 为什么这么容易导致内存泄露:
很重要的一点是因为 RunLoop 会强引用 NSTimer(系统实现的无法做修改)。
所以开发者必须在恰当的时机将NSTimer释放掉。
而一般最佳释放时机为持有 NSTimerselfdealloc 方法里:

- (void)dealloc {[self.timer invalidate];self.timer = nil;
}

iOS10之前的方法,需要传入target(一般我们用self)作为代理,执行需要定时触发的方法。
因为NSTimer会强引用传入的target(这也是系统实现的无法修改)。
当开发者直接传入 self 时,就导致了 self 无法被释放,进而在 dealloc 里释放 NSTimer 的代码也不会执行,从而导致了内存泄露:RunLoop -> NSTimer -> self (不是引用环,但是无法释放)

iOS10苹果新出了3个方法,采用block的形式实现代理方法,不需要传入self(block中还是需要用weakSelf),从而保证了selfdealloc的执行。

更多计时器介绍可见:iOS_定时器:NSTimer、GCDTimer、DisplayLink (最佳实践推荐 6.1)

11.3、malloc -> free

malloc 申请的内存没有使用 free 释放,用 Leaks 检测比较方便:

11.4、CFBridgingRetain - CFBridgingRelease

调用了 CFBridgingRetain 进行 +1 持有后,没有调用 CFBridgingRelease 进行 -1 的:


11.5、被static持有了

例如:用了一个static静态变量记录了上一次滑动的 scrollView,导致退出页面后改 scrollView 没有被释放

/// 记录用户最后滑动的 scrollView (case: 刚拖拽完tab1,立马切换到tab2)
static UIScrollView *gCurrentScrollView = nil; - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {...gCurrentScrollView = scrollView;...
}
- (BOOL)enableHandleScrollView:(UIScrollView *)scrollView {...if (![gCurrentScrollView isEqual:scrollView]) {      return NO;  /// 已经切换tab了,还收到其他tab的回调,不处理 }...
}

修复方案:可以使用代理类若引用该 scrollView:

/// 记录用户最后滑动的 scrollView (case: 刚拖拽完tab1,立马切换到tab2)
static QLWeakProxy *gCurrentScrollViewProxy = nil;- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {...gCurrentScrollViewProxy = [[QLWeakProxy alloc] initWithTarget:scrollView];...
}
- (BOOL)enableHandleScrollView:(UIScrollView *)scrollView {...if (![gCurrentScrollViewProxy.target isEqual:scrollView]) {return NO;  /// 已经切换tab了,还收到其他tab的回调,不处理 }...
}

11.6、单例滥用

一个点赞动效使用了单例,退出直播间没有释放:


12、工具总结:

Memory Report:只能看到内存使用的整体情况,用处不大
Analyze:只能检查编译时期的内存泄漏,不能检测运行时产生的泄露
Leaks:适合发现持续的泄露
Memory Graph:适合发现退出后没有释放的内存泄露
FBRetainCycleDetector:用于查找循环引用链,搭配其他查找泄露对象工具使用
MLeaksFinder:可查找VC和View的泄露,代码开源也可进行DIY拓展


参考:

iOS内存泄漏检查&原理
iOS内存分析原理
检测和诊断 App 内存问题
MLeaksFinder
MLeaksFinder 新特性
MLeaksFinder:精准 iOS 内存泄露检测工具
MLeaksFinder 原理

iOS_Memory Leak 内存泄露治理相关推荐

  1. MFCButton Memory leak(内存泄露问题)

    MFCButton Memory leak(内存泄露问题) http://m.blog.csdn.net/blog/haoekin/8851219 1.无法显示右边箭头的问题 无论怎么折腾都没显示不出 ...

  2. DTrace memory leak 内存泄露

    http://blog.sina.com.cn/s/blog_538040b70100eecn.html 如下程序用于跟踪,在分配和回收都会触发探针 #!/usr/sbin/dtrace -s pid ...

  3. Go内存溢出与内存泄露

    https://www.cnblogs.com/sunsky303/p/11077030.html 一.内存泄露与内存溢出的区别 内存溢出(out of memory,简称OOM) 内存溢出是指程序在 ...

  4. Visual Leak Detector 帮助检查内存泄露

    计算机为 win7,X64,vs2010 http://vld.codeplex.com/releases 从上述地址下载vld-2.3-setup.exe 安装后,打开vs2010在项目属性--VC ...

  5. VC内存泄露检查工具:Visual Leak Detector

    www.diybl.com 时间:2009-04-12 作者:匿名 编辑:sky 初识Visual Leak Detector        灵活自由是C/C++语言的一大特色,而这也为C/C++程序 ...

  6. Android leak内存,GitHub - jin870132/memoryleakdemo: 安卓内存泄露几种常见形式及解决方案...

    安卓内存泄露几种常见形式及解决方案 一.前言 1.内存溢出与内存泄露 内存溢出(oom),是指程序在申请内存时,没有足够的内存空间供其使用,出现oom:比如申请了一个integer,但给它存了long ...

  7. SQL Server 内存泄露(memory leak)——游标导致的内存问题

    原文:SQL Server 内存泄露(memory leak)--游标导致的内存问题 转自:http://blogs.msdn.com/b/apgcdsd/archive/2011/07/01/sql ...

  8. 性能优化之内存泄露(Memory Leak)常用分析工具(另3种)

    1 LeakCanary(最常用,能监控整个App内存泄漏情况) 1.1 使用LeakCanary // 仅在debug包启用LeakCanary debugImplementation 'com.s ...

  9. linux pmap 内存泄露,pmap学习:系统测试中怎么确定内存泄露(memory leak)

    性能测试的一项重要工作就是检查有没有内存泄露.linux下通过top/free/pmap/ps,会提供许多关于内存分配的信息,如top里面的VIRT,RSS,SWAP,VSZ,RES,SHR等等,到底 ...

最新文章

  1. 复杂系统如何在不停机升级同时保持稳定?你必须考虑以下几个点...
  2. [MySQL 5.6] GTID实现、运维变化及存在的bug
  3. canvas绘制线条1像素的问题
  4. 使用开发者工具调试jsp页面中的脚本
  5. C#算法设计排序篇之05-归并排序(附带动画演示程序)
  6. 1018.eclipse工具使用记录
  7. VS2019创建COM组件
  8. rimworld简单机器人mod_rimworld分类技能机器人mod
  9. 社会管理网格化 源码_【西市场快讯】槐荫区委政法委副书记李岩雍赴西市场街道督导网格化管理工作...
  10. 我非英雄,广目无双,我本坏蛋,无限嚣张
  11. LightOJ - 1406 Assassin`s Creed【状压DP】
  12. 期货大佬给交易者的交易箴言,值得珍藏品读!
  13. 树莓派 --- 人脸口罩识别智能监控
  14. python基础知识相关习题
  15. 机器学习 面试题-第二章 线性模型(大厂必问,历经半年整理)
  16. SSH框架电力项目八--运行监控的保存
  17. 可视化百分比数据,Excel图表展示小技巧
  18. 当年做国际医学生实习的一点遗憾
  19. matlab绘制图形hold on_MATLAB中hold on和figure的区别?画三维图为什么一定要meshgrid?...
  20. ProcessDefinition是干这个用的

热门文章

  1. Windows 2000/XP/2003修改输入法列表排列顺序的办法
  2. 域名怎么选择比较合适?
  3. 厦门大学计算机学院控制系,厦门大学自动化系简介
  4. oracle创建用户密码和权限
  5. 转载:一碗牛肉面的思考
  6. (Unity)人物的血条,能量条UI设置,受伤扣血变化等
  7. [转]高并发访问下避免对象缓存失效引发Dogpile效应
  8. 15个实用的webApp前端开发技巧
  9. TK1镜像备份和恢复
  10. Salesforce 发送业务员未提交任务短信提醒经理