使用instrument-->Allocations进行内存分析
使用Instruments可以监测分析app内存相关的 Overall Memory Use、 Leaked Memory、 Abandoned Memory、 Zombies等内容。1、 Allocations点击XCode的Product菜单Profile启动Instruments,选择allocation,会自动启动allocation工具和模拟器。启动后,Allocations面板显示内存
使用Instruments可以监测分析app内存相关的 Overall Memory Use、 Leaked Memory、 Abandoned Memory、 Zombies等内容。
1、 Allocations
点击XCode的Product菜单Profile启动Instruments,选择allocation,会自动启动allocation工具和模拟器。启动后,Allocations面板显示内存中的对象占用曲线,可以显示目前内存中分配了哪些对象,对象的数量,以及由哪些函数进行创建的。
Allocations可以监测到app中一般和虚拟内存的使用情况。统计到的内存类别有All Heap & Anonymous VM、All Heap Allocations、All Anonymous VM。我们无法控制Anonymous VM部分,所以一般只关注All Heap Allocations。
可以对每个动作的前后进行Generations,对比内存的增加,查看使内存增加的具体的方法和代码所在位置。具体操作是在右侧Generation Analysis里点击Mark Generation,这样会产生一个Generation,切换到其他页面或一段时间产生了另外一个事件时再点Mark Generation来产生一个新的Generation,这样反复,生成多个Generation,查看这几个Generation会看到Growth的大小,如果太大可以点进去查看相应占用较大的线程里右侧Heaviest Stack Trace里查看对应的代码块,然后进行相应的处理。
Detail Pane中统计信息包含的类型:Statistics、Call Trees、Allocations List、Generations。
1>Statistics统计项:
2>Call Trees统计项:
3>Allocations List统计项:
4>Generations统计项:
2、Zombies
使用Zombies工具来查找僵尸对象: Zombies工具的查找原理其实和设置NSZombieEnabled环境变量的调试方法是一样的,启动Zombies后在内部设置了NSZombieEnabled为true。
启用了NSZombieEnabled的话,它会用一个僵尸对象来代替已释放对象。也就是在引用计数降到0时,该僵尸实现会将该对象转换成僵尸对象。僵尸对象的作用是在你向它发送消息时,就不会向之前那样Crash或者产生 一个难以理解的行为,而是放出一个错误消息,它会显示一段日志并自动跳入调试器, 因此我们就可以找到具体或者大概是哪个对象被错误的释放了。
3、Leaks
启动Leaks工具后,它会在程序运行时记录内存分配信息和检查是否发生内存泄露。
定位内存泄漏问题:点击Leak Checks时间条的红色叉,查看某行内存泄漏调用栈,会直接跳到内存泄漏代码位置。
Details Pane包含的统计信息有:Leaks、Cycles &Roots、Call Tree。
1>Leaks统计项:
2>Cycles&Roots统计项:
在使用Instruments -> Allocations做内存分析的时候, 我们会看到如下的画面,箭头指向的地方有堆内存heap Allocations,和虚拟内存 Anonymous VM , 到底在手机上什么是堆内存,什么是虚拟内存 Anonymous VM 呢? 在观察内存分配的时候 我们是否需要去了解它?
1) 什么是堆
内存中的堆空间: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。堆里面一般 放的是malloc出来的对象或者数据,资源加载后一般也放在堆里面。一个进程的所有线程共有这些堆空间 ,所以对堆的操作要考虑同步和互斥的问题。
一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表
// 变量所在内存区域
{
int b; //分配在栈空间
char s[] = "abc"; //栈空间
char *p2; //栈
char *p3 = "123456"; //123456{row.content}在常量区,p3在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);//分配得来的10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456{row.content}放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
内存上的堆空间:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才 能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出分配方式:堆都是动态分配的,没有静态分配的堆。
1.1) 堆上消耗的内存
- View 函数的调用
- 注册通知
- 抛出通知
- view 的布局
- 函数代码的执行
- sqlite 数据库的创建
- 向字典中增加对象
等等都需要消耗内存, 上面的代码都是程序员创建的, 程序员去控制堆的内存
1.2) 堆上的内存是否释放
1.2.1) 已经释放的例子:
步骤2) 箭头中有 free 函数, 可以看出, 这个对象 已经被释放
1.2.2) 堆上内存不释放的例子:
上图中箭头执行的地方 没有free 函数 说明 这个对象已经释放
2) Anonymous VM
2.1) 苹果官方文档对虚拟内存的解释
- 更小的内存消耗不仅可以减少内存, 还可以减少cpu 的时间
我们可能会看到这样的情况, All Heap Allocations 是程序真实的内存分配情况,All Anonymous VM则是系统为程序分配的虚拟内存,为的就是当程序有需要的时候,能够及时为程序提供足够的内存空间,而不会现用现创建
Anonymous VM内存是虚拟内存、All Anonymous VM。我们无法准确控制Anonymous VM部分 ,(更新,其实还是可以优化 比如图片绘制相关 详情参见iOS内存探究)
2.2) 问题: 我们需要关注Anonymous VM 内存吗 ?
问答连接
Should you focus on the Live Bytes column for heap allocations or anonymous VM? Focus on the heap allocations because your app has more control over heap allocations. Most of the memory allocations your app makes are heap allocations.
The VM in anonymous VM stands for virtual memory. When your app launches, the operating system reserves a block of virtual memory for your application. This block is usually much larger than the amount of memory your app needs. When your app allocates memory, the operating system allocates the memory from the block it reserved.
Remember the second sentence in the previous paragraph. The operating system determines the size of the virtual memory block, not your app. That’s why you should focus on the heap allocations instead of anonymous VM. Your app has no control over the size of the anonymous VM.
2.3) 不需要关注 Anonymous VM
我们应该关注堆内存, 因为我们对堆内存有更大的掌控, 大部分我们在app的内存分配是堆内存
VM 在匿名空间中代表的是虚拟内存, 当你的app启动的时候, 操作系统为你的应用程序分配内存, 这个分配的虚拟内存一般比你的app需要的内存大很多,
操作系统决定虚拟内存的分配, 而不是你的app, 这就是你为什么要集中精力处理堆内存, 你的app 对虚拟内存没有太多掌控力
2.4) 虚拟内存过大, 解压位图
CGBitmapContextCreateImage 函数会导致虚拟内存过大 ,并且还不释放, 用法未发现问题
CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext);
UIImage *result = [UIImage imageWithCGImage:alphaMaskImage scale:image.scale orientation:image.imageOrientation];
CGImageRelease(alphaMaskImage);
CGContextRelease(alphaOnlyContext);
return result;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)