成功之前我们要做应该做的事情,成功之后我们才可以做喜欢做的事情。

  从苹果的开发者文档里可以看到内存分类如下所示,其中 Leaked memoryAbandoned memory 都属于应该释放而没释放的内存,都是内存泄露(该释放的内存没有释放)。

1.Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

2.Abandoned memory: Memory still referenced by your application that has no useful purpose.

3.Cached memory: Memory still referenced by your application that might be used again for better performance.

一. 内存分析方法

1. 静态分析方法(Analyze)

1.1)Analyze简介

  Clang Static Analyzer是一款静态代码扫描工具,专门用于针对C,C++和Objective-C的程序进行分析。已经被Xcode集成,可以直接使用Xcode进行静态代码扫描分析,也可以单独在命令行下使用并提供html格式的输出报吿和xml格式的结果文件方便集成到Jenkins上进行展示。

  工具:Product->Analyze

  Analyze主要分析以下四种问题:

  • 内存管理错误检查(Memory Error),例如内存泄漏等;
  • 逻辑错误检查(Logic Error):访问空指针或未初始化的变量等;
  • 声明错误检查(Dead Store):也叫无用逻辑存储,其指永远不会被访问的变量、永远不会执行的代码;
  • Api调用错误检查(API Misuse)

  声明错误、逻辑错误、Api调用错误基本在编译时都会有警告,Analyze的主要优势在于静态分析内存泄漏及代码逻辑错误。

  点击Analyze后,Xcode会自动进行编译分析,需要一段时间,之后会像提示警告一样,提示有多少分析的结果。所有的分析结果按照如上的类别,归类显示。点击某个错误的地方,会定位到出错的地方,然后点击向上向下的箭头,会详细展示出出错的步骤。

1.2)内存管理错误检查:Memory Error

  开发的过程中,容易给声明非空的对象赋值nil,导致出错。包括:nil 赋值给了一个期望非空值的指针、Null赋值给非空对象,返回了 nil 值,期望返回一个非空值。

#pragma mark - Memory Error
- (void)MemoryErrorTest {
// nil赋值给了一个期望非空值的指针 nonnull
_userInfo = nil;
} // 返回了nil值,期望返回一个非空值
- (NSArray * _Nonnull)returnValue {
return nil;
}

  Analyze结果:

  内存泄露 Memory (Core Foundation/Objective-C):

  一般来说都是由于使用的CoreFoundation后没有release造成的。在RAC下Foundation框架下的不需要进行release,CoreFoundation框架下仍然需要release。比如在开启arc的环境下,输入以下一段代码:

// 内存泄漏 - 截取部分图像
- (UIImage*)getSubImage:(unsigned long)ulUserHeader
{
UIImage * sourceImage = [UIImage imageNamed:@"image.png"];
CGFloat height = sourceImage.size.height;
CGRect rect = CGRectMake(0 + ulUserHeader*height, 0, height, height); CGImageRef imageRef = CGImageCreateWithImageInRect([sourceImage CGImage], rect);
UIImage* smallImage = [UIImage imageWithCGImage:imageRef];
//CGImageRelease(imageRef); return smallImage;
}

  用注释注释掉CGImageRelease(imageRef)这行,虽然开起了arc,不过仍然会导致imageRef对象泄漏。

  Analyze结果:

  Analyze已经分析出imageRef对象有内存泄漏,这种情况在编译时是无法发现的。

1.3)逻辑错误检查:Logic Error

  初看这段代码,并没有觉得有什么不妥,根据字符串获得index的值。这个前提是字符串一定要按照这个规则提供,如果没有按照这个规则提供,则index就没有值。通过Analyze分析,就检查出来了。

#pragma mark - Logic Error
- (NSInteger)statusIndex:(NSString *)status {
NSInteger index;
if ([status isEqualToString:@"正常"]) {
index = 1;
} else if ([status isEqualToString:@"异常"]) {
index = 2;
} else if ([status isEqualToString:@"严重"]) {
index = 3;
}
return index;
}

  Analyze结果:

1.4)声明错误检查:Dead Store

  很多时候我们创建了一些中间变量需要使用,但是在最终功能的实现上并没有用到这个变量。但是这些变量依然留在代码中,没有删除。这就造成了内存的不必要的开销。这对这部分变量,不需要的时候就要及时的删除。同理:创建类声明的属性,如果没有用到就要及时删除。因为创建类时,会根据类的属性的多少创建对应的内存。

#pragma mark - Dead Store
- (void)deadStoreTest {
NSDictionary *param = @{};
NSString *stackTrace = [param objectForKey:@"key"];
if (!stackTrace) {
stackTrace = @"";
}
// 整个流程stackTrace只是被赋值,没有被使用
}

  Analyze结果:

1.5)Api调用错误检查:API Misuse(Apple)

  API的错误,一般是在大段的逻辑处理中没有注意OC的使用细节。如:数组不能添加空值,数组的元素不能是空值,字典的value不能是空等等。下面这单代码:str 的初始值为空,经过一段逻辑处理后,还是有可能是空,是不能添加到数组中的。

#pragma mark - API Misuse
- (void)addData {
NSString *str = nil;
long number = random();
if (number % 3 == 0) {
str = @"number是3的整数";
} else if (number % 3 == 1) {
str = @"number对3取余为1";
}
NSMutableArray *dataArray = [NSMutableArray arrayWithCapacity:0];
[dataArray addObject:str];
}

  Analyze结果:

2. 动态分析方法(Instruments)

2.1)Instruments简介

  动态分析方法(Instruments)检测程序在运行过程中的内存变化, 是Xcode自带的工具。

  启动Instruments:Xcode -> Product ->Profile 运行 instruments。

  由上Instruments工具面板可以看到很多分析工具,双击和点击choose都可以打开调试 ,项目中用的最多的也就是Allocations和Leaks了,也看自己的需要检测和使用其它的工具。

2.2)Allocations(内存分配分析)

2.2.1)Allocations简介

  内存泄漏是指内存被分配了,但程序中已经没有指向该内存的指针,导致该内存无法被释放,产生内存泄漏。内存不合理运用,苹果官方称这种情况为Abandoned Memory,也就是存在已分配内存的引用,但实际上程序中不会使用,比如图片等对象加入了缓存,但缓存中的对象一直没有被使用。

  XCode提供的Instruments中的Allocations工具可以用来帮你分析内存的分配情况(监测内存分配情况),当你的App收到内存警告、内存暴增,且持续不释放的情况,除了是内存泄漏外,还有就是对性能代码质量不过关导致,这时首先应该用Allocations进行内存分析,了解内存分配情况,哪些对象占用了太多内存。用真机调试比较准确。

  在 ARC 时代更常见的内存泄露是循环引用导致的Abandoned memory,Leaks 工具查不出这类内存泄露,我们也可以通过Allocations内存分析来发现这部分内存泄漏。

2.2.2)Allocations几种分析模式
  • Statistics(静态分析)

2.2.3)如何使用Allocations
(1)准备实例代码

  模拟一段占用内存代码,启动Allocations工具监测内存分配情况,找到高耗内存的代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"id1";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row]; // 模拟占用内存过高的代码
for (int i = 0; i < 200; i++) {
UIImageView *image = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"3.png"]];
image.backgroundColor = [UIColor whiteColor];
image.frame = CGRectMake(0, 0, 30, 30);
[cell.contentView addSubview:image];
} return cell;
}

  启动Instruments -> Allocations工具

(2)Statistics静态分析模式

  启动Allocations默认的数据是Statistics(静态分析)。

  为了更好的解决问题,有必要讲解下这里的内存相关概念:

  • All Heap Allocations:堆上malloc分配的内存,不包过虚拟内存区域;

  • All Anonymous VM:匿名的虚拟内存区域。何为匿名呢?就是Allocations不知道是你哪些代码创建的内存,也就是说这里的内存你无法直接控制。像memory mapped file,CALayer back store等都会出现在这里。这里的内存有些是你需要优化的,有些不是;

    具体数据库分析:

  • Graph:是否需要绘制出来内存曲线;

  • Category:内存类别;

  • Persistent:没有释放的内存大小;

  • # Persistent:没有释放的个数;

  • # Transient:已经释放的个数;

  • Transient Bytes:已经释放的内存大小;

  • Total Bytes:总内存大小(Persistent + Transient Bytes);

  • # Total:总数量(# Persistent + # Transient);

  点击Category小箭头,进入详情页。选择内存异常对象地址,在右侧我们可以看到这个对象是如何被创建的。以及定位到代码。

(3)Mark Generation

  利用Generation,我们可以对内存的增量进行分析,时间戳B相比时间戳A有那些内存增加了多少,对于暴增的地方我们可以很方便发现问题。

  在内存异常的地方做标记:Mark Generation

  设置Call Tree选项(跟检测内存泄漏是一样)

  切换导航到Generations,查看标记样本

  点击标记后面的箭头进一步分析内存分配,定位高内存代码块

(4)Call Tree

  按照类似的方式,这次我们选择Call Tree(调用树)来直接分析代码是如何创建内存的。

(5)Allocations List

  Allocations List提供了一种更纯粹的方式,让你看到内存的分配的列表,我们一般会选择内存从高到低,看看是不是有什么意外分配的大内存块

2.3)Leaks(动态分析)

2.3.1)Leaks简介

  Leaks可以实时看到APP的内存泄漏,我们经常会使用到这个工具。相关工具在XCode的Product->Profile->Instruments-> Leaks或者command+I。

参考:

https://www.jianshu.com/p/4ce4e7cf6a22

2.4)Zombies(僵尸对象分析)

2.4.1)Zombies简介

  Zombies动态分析内存中的僵尸对象,相关工具在XCode的Product->Profile->Instruments->Zombies。那什么是僵尸对象呢?在使用ARC之前,很多人遇到过EXC_BAD_ACCESS错误,这个错误可以理解为访问了已被释放的对象,苹果称之为僵尸对象。

  比如在不开启ARC下,下面这段代码:

NSString *str = [NSString stringWithFormat:@"HBZombie"];
NSLog(@"Go %@",str);
[str release];

  str对象不是手动分配,而是加入到自动释放池,由释放池负责释放,所以第三行调用release时就会产生EXC_BAD_ACCESS错误。

  在开启ARC后,可以很大程度上避免产生EXC_BAD_ACCESS错误,但也是有出现可能的,比如IOS里使用了C++代码,C++部分的对象是不会有ARC来管理的。

  EXC_BAD_ACCESS错误不像访问空指针一样容易定位,往往报错时很难查找到错误点,所以XCode在Instruments中提供了单独的Zombies工具来分析这类错误。

2.4.2)Zombies分析的原理

  和使用Instruments的其他工具一样,点击XCode的Product菜单Profile启动Instruments:

  可以看到Zombies工具下边的介绍,用于查找那些被过度释放的僵尸对象。

  Zombies工具的查找原理其实和设置NSZombieEnabled环境变量的调试方式是一样的,启动Zombies后在内部设置了NSZombieEnabled为True。

  启用了NSZombieEnabled的话,它会用一个僵尸来替换默认的dealloc实现,也就是在引用计数降到0时,该僵尸实现会将该对象转换成僵尸对象。僵尸对象的作用是在你向它发送消息时,就不会向之前那样Crash或者产生 一个难以理解的行为,而是放出一个错误消息,它会显示一段日志并自动跳入调试器, 因此我们就可以找到具体或者大概是哪个对象被错误的释放了。

2.4.3)使用Zombies分析的步骤

(1)启动Instruments;

(2)在模版选择器中,点击Zombies;

(3)选择app和目标设备;

(4)点击选择创建路径文档;

(5)点击工具栏红色圆形按钮或command+r开始记录;

(6)正常使用你的app;如果一个被过度释放的对象被访问了,在timeline窗口里会被插入一个标记同时僵尸对象被会话访问出现。这表示在某个内存地址上一个僵尸对象被访问了。你可以通过点击打开和关闭Zombie Messaged Dialog(僵尸对象访问会话);

(7)点击灰白色横向箭头到僵尸对象的内存地址并且显示僵尸对象详细内存历史的窗口,包括相对应的引用计数和方法调用。

(8)在详细窗口选择Zombie事件(或者是其它你想研究的事件);

(9)输入(Command+3)显示选择事件的栈轨迹的扩展详细区域;

(10)点击Collapse按钮在扩展详细区域隐藏栈轨迹,这样更容易看到你的应用的方法;通过用户icon标志Calls使你的app标记为黑色并置前;

(11)栈轨迹区双击方法显示它的代码在Instruments中;

(12)点击Xcode按钮在详细窗口顶部用于打开这代码在Xcode的编辑界面。

  虽然Instruments可以帮你发现“僵尸”对象,但是你仍然需要仔细检查关系内存历史来确定并解决问题。以下是常见导致僵尸对象的情况。前两个在ARC中应该不会出现,第三个倒是极有可能。

  • release一个已经被release或者autorelease的对象;

  • 对象需要被retain时没有被retain;

  • 一些调用发生在对象被release之后;

2.4.4)手动设置NSZombieEnabled环境变量

  XCode也提供了手动设置NSZombieEnabled环境变量的方法,不过设置NSZombieEnabled为True后,会导致内存占用的增长,同时会影响Leaks工具的调试,这是因为设置NSZombieEnabled会用僵尸对象来代替已释放对象。所以一般不建议进行进行手动设置,而应该使用Zombies工具进行调试。

  点击Product菜单Edit Scheme打开该页面,然后勾选Zombie Objects 复选框:

2.5)Time Profiler(查看时间占用)

  Time Profiler还有上面介绍过的Leaks、Allocations工具,被戏称为Instruments的救命三招,是当应用遇到问题时首先应当使用的三个工具。

  Time Profiler帮助我们分析代码的执行时间,找出导致程序变慢的原因,告诉我们“时间都去哪儿了?”。

  Time Profiler分析原理:它按照固定的时间间隔来跟踪每一个线程的堆栈信息,通过统计比较时间间隔之间的堆栈状态,来推算某个方法执行了多久,并获得一个近似值。其实从根本上来说与我们的原始分析方法异曲同工,只不过其将各个方法消耗的时间统计起来。

  选择Time Profiler工具开始测试,这时会自动启动模拟器和Time Profiler录制。

  先进行一些App的操作,让Time Profiler收集足够的数据,尤其是你觉得那些有性能瓶颈的地方。

二.第三方检测方法

MLeaksFinder

参考:

https://blog.csdn.net/Alpaca12/article/details/80157520

http://events.jianshu.io/p/b8d2f736ae6b

https://www.jianshu.com/p/70184408649a

iOS性能优化之内存分析的更多相关文章

  1. redis性能优化、内存分析及优化

    redis性能优化.内存分析及优化 1.优化网络延时 2.警惕执行时间长的操作 3.优化数据结构.使用正确的算法 4.考虑操作系统和硬件是否影响性能 5.考虑持久化带来的开销 5.1 RDB 全量持久 ...

  2. iOS性能优化之内存管理:Analyze、Leaks、Allocations的使用和案例代码

    最近接了个小任务,和公司的iOS小伙伴们分享下instruments的具体使用,于是有了这篇博客...性能优化是一个很大的话题,这里讨论的主要是内存泄露部分. 一. 一些相关概念 很多人应该比较了解这 ...

  3. iOS性能优化之内存(memory)优化

    https://www.jianshu.com/p/8662b2efbb23 近期在工作中,对APP进行了内存占用优化,减少了不少内存占用,在此将经验进行总结和分享,也欢迎大家进行交流. 在优化的过程 ...

  4. 【腾讯Bugly干货分享】微信读书iOS性能优化

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...

  5. <转>iOS性能优化:Instruments使用实战

    最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...

  6. iOS性能优化:Instruments使用实战

    iOS性能优化:Instruments使用实战   最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...

  7. iOS性能优化

    最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...

  8. iOS - 性能优化:Instruments使用简介

    最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...

  9. IOS 性能优化的建议和技巧

    IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...

  10. 老李分享:Android性能优化之内存泄漏1

    老李分享:Android性能优化之内存泄漏   前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我 ...

随机推荐

  1. Pandas: 将dataframe转换为dict

    背景 将Dataframe的每一列数据转换成字典并保存.也就是字段名变为key, 数值变为value. 方案 以下是效果图 参考链接 https://blog.csdn.net/hanyunkaka/ ...

  2. 小白也能搞定!Windows10上CUDA9.0+CUDNN7.0.5的完美安装教程

    前言: 为什么要在本地电脑安装 CUDA,CUDA 是什么的,用来做什么?我想,点击标题进来的小伙伴,应该都清楚这些.不管你是用来做什么,或者跟我一样为了跑 Tensorflow 的 Object D ...

  3. MyBatis Mapper映射处理CLOB和BLOB类型

    ​Mybatis的MapperXML映射文件应该处理数据库字段类型为CLOB和BLOB类型的数据呢?首先我们先看下CLOB和BLOB这两种数据类型的介绍. 介绍 使用Mybatis时涉及到两种特殊类型 ...

  4. The database operation was expected to affect 1 row(s), but actually affected 0 row(s); 解决乐观并发

    The database operation was expected to affect 1 row(s), but actually affected 0 row(s); 解决乐观并发 1.乐观并 ...

  5. 关于ChatGPT的一些闲扯淡(1)

    这篇写的有点迟了,前者子ChatGPT正火的时候,懒病发作一直拖延.今天对ChatGPT做一个简单的讨论,也是把学习的心得和大家分享一下. 首先什么是GPT,英文全称是Generative Pretr ...

  6. Python之os模块常用命令

    OS模块介绍 os模块是Python标准库中的一个用于访问操作系统相关功能的模块,os模块提供了一种可移植的使用 操作系统功能的方法.使用os模块中提供的接口,可以实现跨平台访问.该模块包含了大量的操 ...

  7. vs2022离线安装教程

    因特殊需要,要离线安装vs2022的环境,完成配置后将安装过程记录. 第一步:下载visual Studio 引导程序以创建布局 在微软的官网下载合适的引导程序. 官网地址:创建基于网络的安装 - V ...

  8. 如何将项目打包上传到NuGet服务器?

    作者:西瓜程序猿 主页传送门:https://www.cnblogs.com/kimiliucn 前言 在我写[在.NET Framework中使用RocketMQ(阿里云版)]这篇博客的时候,因为封 ...

  9. C#结合OpenCVSharp4使用直方图算法比较图片相似度

    C#结合OpenCVSharp4使用直方图算法比较图片相似度 直方图有灰度直方图.颜色直方图,如果是灰度图像,那么就用灰度直方图,这里使用颜色直方图来计算两个图片的相似度. 这里只记录如何使用,至于算 ...

  10. 搭一下 Stable Diffusion WebUI

    Preface 前不久看到好多朋友用上Stable Diffusion来做原画,然后又配合上了Chatgpt. 一直以来都想尝试一下,奈何2014款的双核mac跑个idea都发出了拖拉机的轰鸣声. 所 ...