[关闭]
@qidiandasheng 2022-08-23T01:28:02.000000Z 字数 6742 阅读 3423

内存监控:Instruments和三方库(😁)

性能优化


Instruments

关于线下性能监控,苹果公司官方就有一个性能监控工具 Instruments。它是一款被集成在 Xcode 里,专门用来在线下进行性能分析的工具。

Instruments 的功能非常强大,比如说 Energy Log 就是用来监控耗电量的,Leaks 就是专门用来监控内存泄露问题的,Network 就是用来专门检查网络情况的,Time Profiler 就是通过时间采样来分析页面卡顿问题的。

如下图所示,就是 Instruments 的各种性能检测工具。

下载.png-1087.7kB

截屏2020-07-17 下午8.38.28.png-222.2kB

Allocations

通常是拿来分析内存增加(不一定是内存泄漏)和app中各部分占用内存问题,当我们得知哪个内存占用比较多,我们直接进行优化即可减少内存占用问题。

这是我创建的一个ViewController,我们后面的测试会通过不断的在主页push和pop这个controller来看一个内存的变化。

  1. @implementation DSOneViewController
  2. - (void)viewDidLoad {
  3. [super viewDidLoad];
  4. [UIColor redColor];
  5. self.myNames = [NSMutableArray array];
  6. for (int i=0; i<100000; i++) {
  7. // 这个字符串对象占用48个字节
  8. NSString *num = [NSString stringWithFormat:@"测试字符串:%d",i];
  9. [self.myNames addObject:num];
  10. }
  11. }
  12. @end

Statistics

默认选择的是Statistics,表示一个All Heap & Anonymous VM的内存使用量。如下图1部分表示一个总的内存,2部分表示单独某一类对象占用的内存:

截屏2020-07-17 下午8.52.23.png-538.3kB

All Heap Allocations

App运行过程中在堆上分配的内存,几乎所有类实例,包括 UIViewController、UIView、UIImage、Foundation 和我们代码里的各种类/结构实例。一般和我们的代码直接相关。

All Anonymous VM

都是由VM:开头的,无法由开发者直接控制,主要包含一些系统模块的内存占用。有些部分虽然看起来离我们的业务逻辑比较远,但其实是保证我们代码正常运行不可或缺的部分,也是我们常常忽视的部分。

例如图片之类的大内存,属于All Anonymous VM -> VM: ImageIO_IOSurface_Data,其他的还有IOAccelerator与IOSurface等跟GPU关系比较密切的。


Call Tree

把列表展示类型切换成Call Trees,能够非常清晰的看到调用树。采集的是分配内存相关的方法调用。

注意:这里显示的方法表示占用的内存是在这个方法里申请的,不代表这个方法对应的实例未被释放。比如我在这个方法里创建了一个单例,当我们这个方法对应的实例释放的时候,我们还能看到列表中有这个实例对应的方法,这就是告诉你之前这个单例是在这个方法里创建的。

底部Call Tree选择Invert Call TreeHide System Libraries,用来筛选处理调用树,看起来更加简单明了。

注意:
同一个类的不同方法都会申请函数内存,比如[UIFont fontWithName:@"Arial-BoldMT" size:15];[UIFont systemFontOfSize:12];。但[UIColor blackColor];[UIColor redColor];却不会,估计是调用的同一个方法。

查看快照(Mark Generation)

点击Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息。

比如我进入DSOneViewController前点击了一下创建快照A,然后push进DSOneViewController等字符串创建完毕之后又点击了一下创建快照B,如下图所示显示了这段时间内新分配的内存:
截屏2020-07-17 下午10.32.31.png-403.3kB

如图所示我们在快照B期间一共创建了100000个字符串,每个48字节,48*100000/1024/1024=4.577 M,四舍五入跟我们图中显示分配的内存一样。

记住这个快照是动态的,我们在pop回首页的时候,创建快照C,我们看到快照B内分配的一些内存被释放了:
截屏2020-07-17 下午10.36.26.png-278.6kB

使用场景:

我们可以不断重复 push 和 pop 同一个 UIViewController,理论上来说,push 之前跟 pop 之后,app 会回到相同的状态。因此,在 push 过程中新分配的内存,在 pop 之后应该被 dealloc 掉,除了前几次 push 可能有预热数据和 cache 数据的情况。如果在数次 push 跟 pop 之后,内存还不断增长,则有内存泄露。因此,我们在每回 push 之前跟 pop 之后,都 Mark Generation 一下,以此观察内存是不是无限制增长。这个方法在 WWDC 的视频里:Session 311 - Advanced Memory Analysis with Instruments,以及苹果的开发者文档:Finding Abandoned Memory 里有介绍。

用这种方法来发现内存泄露还是很不方便的:

Leaks

选择Leaks,点击运行,可以看到默认分上下两部分,上面部分就是Allocations,跟我们上面的一样。
截屏2020-07-19 下午3.46.49.png-564.1kB

选择下面的话就是Leaks内存泄漏检测,如果无法定位到具体代码,则修改Buidl Setting->Debug Information Format->Debug里选择DWARF with dSYM File。重新运行看到红叉出现,即是出现了内存泄漏。

下面的列表选择Call Trees,选择Hide System Libiraries即可显示内存泄漏具体的方法。
截屏2020-07-19 下午4.01.11.png-443.8kB

双击即可查看方法里面具体内存泄漏的地方:
截屏2020-07-19 下午4.20.43.png-452.7kB

注意无法显示具体方法

第一次运行的时候我们能看到是可以的,当第二次直接点击暂停再重新运行的时候,虽然我们能看到内存泄漏的红叉,但无法定位到具体的方法,提示如下,大致意思就是之前已经定位过了,无法追踪调用栈,估计是有缓存,但我不知道在哪清。

暴力解决方法把instruments关了重新打开即可。

截屏2020-07-19 下午4.45.00.png-516kB

Xcode Debug Memory Graph

代码运行并调用UIImage *image = [UIImage imageNamed:@"apic"];读取本地图片之后,如下图选择Debug Memory Graph

截屏2020-11-06 上午8.49.14.png-180.8kB

然后可以点击 File->Export Memory Graph 将其导出为 memgraph 文件。

vmmap - 查看虚拟内存

Image IO部分内存:

我上面的测试图片大小为:650*1015*4/1000=2639kb。使用vmmap查看上面生成的memgraph 文件。

  1. vmmap --summary DSImageBitmaps.memgraph

截屏2020-11-06 上午8.56.56.png-2803.6kB

我们可以看到Image IO刚好就是差不多2604K。也可以使用以下方法进行筛选:

  1. vmmap --summary DSImageBitmaps.memgraph | grep 'Image IO'

总内存:

app的内存占用主要是Dirty Size+Swapped Size,我们看到这里差不多是22.1M,但是我们查看xcode的Debug Navigator时是18.9M。

主要原因是如果你的App使用了别的进程创建的共享内存,那么Debug Navigator是不会将它计入你自己的内存总量的,不过vmmap会将它加入TOTAL中,所以可能会导致vmmap计算的内存量会大于Debug Navigator统计内存量。

截屏2020-11-06 上午9.02.46.png-514.6kB

Facebook内存监测库

Facebook工程师们开源了一些自动化工具来解决监测内存泄露问题:FBRetainCycleDetectorFBAllocationTrackerFBMemoryProfiler

原文介绍:Automatic memory leak detection on iOS

中文翻译:在iOS上自动检测内存泄露

MLeaksFinder内存泄漏监测

这里有一个微信读书团队开源的工具MLeaksFinder,它可以在你程序运行期间,如果有内存泄漏就会弹出提示告诉你泄漏的地方。

具体原理如下

我们知道,当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view,view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController,它的 view,view 的 subviews 等等是否还存在。

具体的方法是,为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。

  1. - (BOOL)willDealloc {
  2. __weak id weakSelf = self;
  3. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  4. [weakSelf assertNotDealloc];
  5. });
  6. return YES;
  7. }
  8. - (void)assertNotDealloc {
  9. NSAssert(NO, @“”);
  10. }

这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若3秒后没被释放,就会中断言。

参考

iOS内存深入研究

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注