Instruments使用总结
一、Instruments介绍
Instruments 一个很灵活的、强大的工具,是性能分析、动态跟踪 和分析OS X以及iOS代码的测试工具,用它可以极为方便收集关于一个或多个系统进程的性能和行为的数据,并能及时随着时间跟踪而产生的数据,并检查所收集的数据,还可以广泛收集不同类型的数据.也可以追踪程序运行的过程,这样instrument就可以帮助我们了解用户的应用程序和操作系统的行为。
总结一下instrument能做的事情:
1. Instruments是用于动态调追踪和分析OS X和iOS的代码的性能分析和测试工具;
2.Instruments支持多线程的调试;
3.可以用Instruments去录制和回放,图形用户界面的操作过程
4.可将录制的图形界面操作和Instruments保存为模板,供以后访问使用。
instrument还可以:
1.追踪代码中的(甚至是那些难以复制的)问题;
2.分析程序的性能;
3.实现程序的自动化测试;
4.部分实现程序的压力测试;
5.执行系统级别的通用问题追踪调试;
6.使你对程序的内部运行过程更加了解。
打开方式:
Xcode -> Open Developer Tool -> Instruments
其中比较常用的有四种:
- Allocations:用来检查内存分配,跟踪过程的匿名虚拟内存和堆的对象提供类名和可选保留/释放历史
- Leaks:一般的查看内存使用情况,检查泄漏的内存,并提供了所有活动的分配和泄漏模块的类对象分配统计信息以及内存地址历史记录
- Time Profiler:分析代码的执行时间,执行对系统的CPU上运行的进程低负载时间为基础采样
- Zombies:检查是否访问了僵尸对象
其他的:
- Blank:创建一个空的模板,可以从Library库中添加其他模板
- Activity Monitor:显示器处理的CPU、内存和网络使用情况统计
- Automation:用JavaScript语言编写,主要用于分析应用的性能和用户行为,模仿/击发被请求的事件,利用它可以完成对被测应用的简单的UI测试及相关功能测试
- Cocoa Layout:观察约束变化,找出布局代码的问题所在。
- Core Animation:用来检测Core Animation性能的,给我们提供了周期性的FPS,并且考虑到了发生在程序之外的动画,界面滑动FPS可以进行测试
- Core Data:监测读取、缓存未命中、保存等操作,能直观显示是否保存次数远超实际需要
- Energy Diagnostic :用于Xcode下的Instruments来分析手机电量消耗的。(必须是真机才有电量)
- GPU Driver :可以测量GPU的利用率,同样也是一个很好的来判断和GPU相关动画性能的指示器。它同样也提供了类似Core Animtaion那样显示FPS的工具。
- Network:分析应用程序如何使用TCP / IP和UDP / IP连接使用连接仪器。就是检查手机网速的。(这个最好是真机)
二、Allocations(分配)
1.内存分类:
- Leaked memory:泄漏的内存,如为对象A申请了内存空间,之后再也没用到A,也没有释放A导致内存泄漏(野指针。。。)
- Abandoned memory:被遗弃的内存,如循环引用,递归不断申请内存而导致的内存泄漏
- Cached memory:缓存的内存
2.Abandoned memory
其中内存泄漏我们可以用Leaks
,野指针可以用Zombies
(僵尸对象),而在这里我们就可以用Allocations
来检测Abandoned memory
的内存。
即我们采用Generational Analysis的方法来分析,反复进入退出某一场景,查看内存的分配与释放情况,以定位哪些对象是属于Abandoned Memory的范畴。
在Allocations工具中,有专门的Generational Analysis设置,如下:
我们可以在程序运行时,在进入某个模块前标记一个Generation,这样会生成一个快照。然后进入、退出,再标记一个Generation,如下图:
4.png在详情面板中我们可以看到两个Generation间内存的增长情况,其中就可能存在潜在的被遗弃的对象,如下图:
5.png其中growth就是我们增长的内存,GenerationA是程序启动到进入该场景增长的内存,GenerationB就是第二次进入该场景所增长的内存,查看子类可以发现有两个管理类造成了Abandoned memory
。
3.设置Generations
使用instrument测试内存泄露 工具 Allocations 测试是否内存泄露 使用标记,可以更省事省力的测试页面是否有内存泄露
1)设置Generations
2)选择mark generation
7.jpg3)使用方法 在进入测试页面之前,mark一下----->进入页面----->退出----->mark------>进入------->退出------->mark------>进入如此往复5、6次,就可以看到如下结果
8.jpg这种情况下是内存有泄露,看到每次的增量都是好几百K或者上M的,都是属于内存有泄露的,这时候就需要检测下代码一般情况下,100K以下都属于正常范围,growth表示距离你上次mark的增量
三、Leaks(泄漏)
1.内存溢出和内存泄漏的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
在前面的ALLcations里面我们提到过内存泄漏就是应该释放而没有释放的内存。而内存泄漏分为两种:Leaked Memory 和 Abandoned Memory。前面我们讲到了如何找到Abandoned Memory被遗忘的内存,现在我们研究的就是Leaked Memory。
以发生的方式来分类,内存泄漏可以分为4类:
常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
影响:从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
下边我们介绍Instruments里面的Leaked的用法,首先打开Leaked,跑起工程来,点击要测试的页面,如果有内存泄漏,会出现下图中的红色的❌。然后按照后边的步骤进行修复即可
1.png 2.png上面的旧版的样式,下面的是新版的样式,基本操作差不多
Snip20181211_2.png 3.png在详情面板选中显示的若干条中的一条,双击,会自动跳到内存泄露代码处,然后点击右上角 Xcode 图标进行修改。
下图是对Leaked页面进一步的理解:
4.png内存泄漏动态分析技巧:
- 在 Display Settings 界面建议把 Snapshot Interval (snapʃɒt, 数据快照)间隔时间设置为10秒,勾选Automatic Snapshotting,Leaks 会自动进行内存捕捉分析。(新版本直接在底部修改)
- 熟练使用 Leaks 后会对内存泄漏判断更准确,在可能导致泄漏的操作里,在你怀疑有内存泄漏的操作前和操作后,可以点击 Snapshot Now 进行手动捕捉。
- 开始时如果设备性能较好,可以把自动捕捉间隔设置为 5 秒钟。
- 使用ARC的项目,一般内存泄漏都是 malloc、自定义结构、资源引起的,多注意这些地方进行分析。
- 开启ARC后,内存泄漏的原因
开启了ARC并不是就不会存在内存问题,苹果有句名言:ARC is only for NSObject。
注:如果你的项目使用了ARC,随着你的操作,不断开启或关闭视图,内存可能持续上升,但这不一定表示存在内存泄漏,ARC释放的时机是不固定的。
这里对 Display Settings中 的 Call tree
选项做一下说明 [官方user guide翻译]:
- Separate By Thread:线程分离,只有这样才能在调用路径中能够清晰看到占用CPU最大的线程.每个线程应该分开考虑。只有这样你才能揪出那些大量占用CPU的"重"线程,按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。
- Invert Call Tree:从上到下跟踪堆栈信息.这个选项可以快捷的看到方法调用路径最深方法占用CPU耗时(这意味着你看到的表中的方法,将已从第0帧开始取样,这通常你是想要的,只有这样你才能看到CPU中花费时间最深的方法),比如FuncA{FunB{FunC}},勾选后堆栈以C->B->A把调用层级最深的C显示最外面.反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。
- Hide Missing Symbols:如果dSYM无法找到你的APP或者调用系统框架的话,那么表中将看到调用方法名只能看到16进制的数值,勾选这个选项则可以隐藏这些符号,便于简化分析数据.
- Hide System Libraries:表示隐藏系统的函数,调用这个就更有用了,勾选后耗时调用路径只会显示app耗时的代码,性能分析普遍我们都比较关系自己代码的耗时而不是系统的.基本是必选项.注意有些代码耗时也会纳入系统层级,可以进行勾选前后前后对执行路径进行比对会非常有用.因为通常你只关心cpu花在自己代码上的时间不是系统上的,隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。隐藏缺失符号。如果 dSYM 文件或其他系统架构缺失,列表中会出现很多奇怪的十六进制的数值,用此选项把这些干扰元素屏蔽掉,让列表回归清爽。
- Show Obj-C Only:只显示oc代码 ,如果你的程序是像OpenGl这样的程序,不要勾选侧向因为他有可能是C++的
- Flatten Recursion:递归函数, 每个堆栈跟踪一个条目,拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。
- Top Functions:找到最耗时的函数或方法。 一个函数花费的时间直接在该函数中的总和,以及在函数调用该函数所花费的时间的总时间。因此,如果函数A调用B,那么A的时间报告在A花费的时间加上B.花费的时间,这非常真有用,因为它可以让你每次下到调用堆栈时挑最大的时间数字,归零在你最耗时的方法。
四、Time Profiler(时间分析器)
用来检测app中每个方法所用的时间,并且可以排序,并查找出哪些函数占用了大量时间。
使用Time Profile前有两点需要注意的地方:
1、一定要使用真机调试
在开始进行应用程序性能分析的时候,一定要使用真机。因为模拟器运行在Mac上,然而Mac上的CPU往往比iOS设备要快。相反,Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用CAEAGLLayer来写一些OpenGL的代码时候,这就导致模拟器性能数据和用户真机使用性能数据相去甚远
2、应用程序一定要使用发布配置
在发布环境打包的时候,编译器会引入一系列提高性能的优化,例如去掉调试符号或者移除并重新组织代码。另iOS引入一种"Watch Dog"[看门狗]机制,不同的场景下,“看门狗”会监测应用的性能,如果超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程。开发者可以crashlog看到对应的日志,但Xcode在调试配置下会禁用"Watch Dog"
1)界面详情:
1.png2)详细面板
5.png主要是看Call Tree和Sample List这两种视图:
3)调用树
4.pngRunning Time:函数运行的时间,这个时间是累积时间
Self:在栈顶次数
Symbol Name:被调用函数的符号信息
4)详情面板更多的信息选项
3.png5)样本列表
2.png五、Zombies(僵尸)
1.概念
翻译英文:专注于检测过度释放的“僵尸”对象。还提供了数据对象分配的类以及所有活动分配内存地址的历史。
这里我们可以看到一个词语叫“over-release”,过度释放。我们在项目中见到最多的就是“EXC_BAD_ACCESS”或者是这样的:Thread 1: Program received signal:"EXC_BAD_ACCESS",这就是访问了被释放的内存地址造成的。
过度释放,是对同一个对象释放了过多的次数,其实当引用计数降到0时,对象占用的内存已经被释放掉,此时指向原对象的指针就成了“悬垂指针”,如若再对其进行任何方法的调用,(原则上)都会直接crash(然而由于某些特殊的情况,不会马上crash)。过度释放简单的说就是对release的对象再release,就是过度释放。
我们需要知道这几个概念:
1、内存泄漏:对象使用完没有释放,导致内存浪费。
2、僵尸对象:已经被销毁的对象(不能再使用的对象)
3、野指针:指向僵尸对象(不可用内存)的指针。给野指针发消息会报EXC_BAD_ACCECC错误。
4、空指针:没有指向储存空间的指针(里面存的是nil,也就是0)。在oc中使用空指针调中方法不会报错。
注意:为了避免野指针错误的常见方法:在对象被销毁之后,将指向对象的指针变为空指针。
对于过度释放的问题,可以直接使用Zombie,当过度释放发生时会立即停在发生问题的位置,同时结合内存分配释放历史和调用栈,可以发现问题。至于上文提到的不会crash的原因,其实有很多,比如:
对象内存释放时,所用内存并没有完全被擦除,仍有旧对象部分数据可用
原内存位置被写入同类或同样结构的数据
2.原理
我们将僵尸对象“复活”的目的:僵尸对象就是让已经释放了的对象重新复活,便于调试;是为了让已经释放了的对象在被再次访问时能够输出一些错误信息。其实这里的“复活”并不是真的复活,而是强行不死:这么说吧 相当于 他的RC=0的时候 系统再强行让他RC=1,顺便打上一个标记 zoom,等到你去掉那个沟以后 系统会把带有标记zoom的对象RC=0。
3.用法
下边是Instruments里面的Zombies的用法:
在Launch Configuration中勾选Record reference counts和Enable NSZombie detection。其中Recordreference counts是显示引用计数,Enable NSZombie detection是能够检测僵尸对象。
1.png这样在程序运行的时候,如果发现僵尸对象它就会弹出一个对话框,点击其中“→”按钮,在屏幕的下方会显示僵尸对象的详细信息,下图可以看到僵尸对象的引用计数变化情况。
2.png注意:Zombies模版在使用的时候会导致内存的飙升,这是因为所有被释放的对象被僵尸对象取代,并未真的释放掉,在结束Zombies时会释放,这是预知行为,这就意味着instrument里的其它工具和Zombies是不能同时使用的,Zombies会导致其它的数据不准。包括leaks,你也不应该把它加到Zombies模版中,即使这么做了结果也没什么意义。对于iOS应用来说,在用Zombies模版时使用iOS模拟器比真机要好。
另外XCode也提供了手动设置NSZombieEnabled环境变量的方法,不过设置NSZombieEnabled为True后,会导致内存占用的增长,同时会影响Leaks工具的调试,这是因为设置NSZombieEnabled会用僵尸对象来代替已释放对象。
点击Product菜单Edit Scheme打开该页面,然后勾选Enable Zombie Objects复选框:
3.png最后提醒的是NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存,一直开启的话,该死去的对象会一直存在,后果可想而知,自重!
六、扩展
-
野指针
- C语言: 当我们声明1个指针变量,没有为这个指针变量赋初始值.这个指针变量的值是1个垃圾指 指向1块随机的内存空间。
- OC语言: 指针指向的对象已经被回收掉了.这个指针就叫做野指针.
- 僵尸对象
-
内存回收的本质.
- 申请1块空间,实际上是向系统申请1块别人不再使用的空间.
- 释放1块空间,指的是占用的空间不再使用,这个时候系统可以分配给别人去使用.
- 在这个个空间分配给别人之前 数据还是存在的.
- OC对象释放以后,表示OC对象占用的空间可以分配给别人.
- 但是再分配给别人之前 这个空间仍然存在 对象的数据仍然存在.
- 僵尸对象: 1个已经被释放的对象 就叫做僵尸对象.
-
使用野指针访问僵尸对象.有的时候会出问题,有的时候不会出问题.
- 当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候, - 这个时候其实是可以访问的.
- 因为对象的数据还在.
- 当野指针指向的对象所占用的空间分配给了别人的时候 这个时候访问就会出问题.
- 所以,你不要通过1个野指针去访问1个僵尸对象.
- 虽然可以通过野指针去访问已经被释放的对象,但是我们不允许这么做.
-
僵尸对象检测.
- 默认情况下. Xcode不会去检测指针指向的对象是否为1个僵尸对象. 能访问就访问 不能访问就报错.
- 可以开启Xcode的僵尸对象检测.
- 那么就会在通过指针访问对象的时候,检测这个对象是否为1个僵尸对象 如果是僵尸对象 就会报错.
- 为什么不默认开启僵尸对象检测呢?
- 因为一旦开启,每次通过指针访问对象的时候.都会去检查指针指向的对象是否为僵尸对象.
- 那么这样的话 就影响效率了.
-
- 如何避免僵尸对象报错.
- 当1个指针变为野指针以后. 就把这个指针的值设置为nil
- 僵尸对象无法复活.
- 当1个对象的引用计数器变为0以后 这个对象就被释放了.
- 就无法取操作这个僵尸对象了. 所有对这个对象的操作都是无效的.
- 因为一旦对象被回收 对象就是1个僵尸对象 而访问1个僵尸对象 是没有意义.
好了,以上就是对于
Instruments
工具的使用总结
喜欢的就点个赞👍吧
参考文章
Allocations:Xcode8 Instruments 测试工具使用三
Leaks:Xcode 8 Instruments 学习(一)
Time Profiler:Xcode 8 Instruments 学习(二)
Zombies:Instruments性能检测