iOS性能优化之内存分析
成功之前我们要做应该做的事情,成功之后我们才可以做喜欢做的事情。
从苹果的开发者文档里可以看到内存分类如下所示,其中 Leaked memory
和 Abandoned 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。
参考:
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
iOS性能优化之内存分析的更多相关文章
- redis性能优化、内存分析及优化
redis性能优化.内存分析及优化 1.优化网络延时 2.警惕执行时间长的操作 3.优化数据结构.使用正确的算法 4.考虑操作系统和硬件是否影响性能 5.考虑持久化带来的开销 5.1 RDB 全量持久 ...
- iOS性能优化之内存管理:Analyze、Leaks、Allocations的使用和案例代码
最近接了个小任务,和公司的iOS小伙伴们分享下instruments的具体使用,于是有了这篇博客...性能优化是一个很大的话题,这里讨论的主要是内存泄露部分. 一. 一些相关概念 很多人应该比较了解这 ...
- iOS性能优化之内存(memory)优化
https://www.jianshu.com/p/8662b2efbb23 近期在工作中,对APP进行了内存占用优化,减少了不少内存占用,在此将经验进行总结和分享,也欢迎大家进行交流. 在优化的过程 ...
- 【腾讯Bugly干货分享】微信读书iOS性能优化
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...
- <转>iOS性能优化:Instruments使用实战
最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...
- iOS性能优化:Instruments使用实战
iOS性能优化:Instruments使用实战 最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...
- iOS性能优化
最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...
- iOS - 性能优化:Instruments使用简介
最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...
- IOS 性能优化的建议和技巧
IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...
- 老李分享:Android性能优化之内存泄漏1
老李分享:Android性能优化之内存泄漏 前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我 ...
随机推荐
- vlunhub笔记(二)earth
(一)信息收集 开始扫描目标机ip,目标机ip:192.168.241.135 arp-scan -l 直接访问目标 ip 192.168.241.135 发现400报错 只能先去考虑扫一下信息 ...
- 如何正确使用:has和:nth-last-child
我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字.例如,一个拥有三个或更多子项的grid.你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会根据子元素的数量而改变. ...
- HTML5CSS3基础
目录 HTML5CSS3基础 1 2D 转换 1.1 二维坐标系 1.2 2D 转换之移动 translate 1.3 2D 转换之旋转 rotate 1.4 2D 转换中心点 transform-o ...
- 最常用的Linux命令
1. tar 创建一个新的tar文件 $ tar cvf archive_name.tar dirname/ 解压tar文件 $ tar xvf archive_name.tar 查看tar文件 $ ...
- svg动画 - 波浪动画
波浪 <path d="M 96.1271 806.2501 C 96.1271 806.2501 241.441 755.7685 384.5859 755.7685 C 529.8 ...
- 文心一言 VS 讯飞星火 VS chatgpt (81)-- 算法导论7.4 6题
六.如果用go语言,考虑对 PARTITION 过程做这样的修改:从数组 A 中随机选出三个元素,并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分.求以a 的函数形式表示的.最坏 ...
- C++的动态分派在HotSpot VM中的重要应用
众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定.C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持 ...
- Dubbo3应用开发—Dubbo服务管理平台DubboAdmin介绍、安装、测试
Dubbo服务管理平台 DubboAdmin的介绍 Dubbo Admin是Apache Dubbo服务治理和管理系统的一部分. Dubbo Admin提供了一套用于服务治理的Web界面,让我们可以更 ...
- 实训——基于大数据Hadoop平台的医疗平台项目实战
文章目录 医疗平台项目描述 数据每列的含义 数据分析业务需求 架构图 成果图 环境搭建 非常感谢各位的认可,最近太多人找我问东问西,故在此进行说明一下: 首先这个是在Linux上基于Hadoop的搭建 ...
- Python网络编程——操作系统基础、网络通信原理、.网络通信实现、DNS域名解析、 网络通信流程
文章目录 一.操作系统基础 二.网络通信原理 2.1 互联网的本质就是一系列的网络协议 2.2 osi七层协议 2.3 tcp/ip五层模型讲解 2.3.1 物理层 2.3.2 数据链路层 2.3.3 ...