iOS内存管理之一:内存分配与概述
一、概述
在iOS系统中,程序运行时会将所需的数据、图片、文件等资源加载到内存。不同类型的数据资
源会加载到不同的内存分区,这些数据资源有的在程序运行时常驻内存,有的会在用完即释放掉。
常驻内存的数据资源所占内存我们暂命名为基础占用内存。当程序执行某个方法时,会在栈空间加载一些数据资源,这时占用内存会升高,函数执行完内存即释放掉。如果执行某个函数过程中加载资源过多,内存占用超出了系统限制,程序会被杀死。如果方法执行过程中对一些生成对象没有做好处理,则会出现内存泄露。
二、内存分区
下图是由C/C++/OBJC编译的程序占用内存分布的结构:
注:此处堆栈空间不是数据结构中的堆栈,两者没有关系,不要混淆。
1.栈区 (stack)
栈区由编译器在需要的时候自动分配,不需要的时候自动清除。栈中主要存放局部变量、函数传的参数等。栈是系统的数据结构,应唯一的进程/线程。
栈主要有静态分配和动态分配两种分配方式。无论是以哪种方式分配的空间,都无需手动管理释放。相比于堆,栈更加快速高效。
2.堆区(heap)
堆区是由程序员分配和释放的,如果不去释放,在程序结束时候,系统可能会回收内存。但是编程人员应该养成良好习惯,时刻记得处理所分配的内存。在iOS中,使用alloc方式生成的对象都是存放在堆中。
堆区采用链表式管理,在分配内存时操作系统有一个记录空闲内存地址的链表,当接收到程序分配内存的申请时,操作系统就会遍历该链表,遍历到一个记录的内存地址大于申请内存的链表节点,并将该节点从该链表中删除,然后将该节点记录的内存地址分配给程序。不过容易产生内存碎片。
3.全局区/静态区(static)
全局区用来存放静态变量和全局变量,全局区又分为全局初始化区和全局未初始化区,用来存放已经初始化和未初始化的全局变量&静态变量。程序结束后由系统释放。
4.文字常量区
文字常量区用来存放常量字符串,程序调用结束后由系统释放。
5.代码区
代码区用来存放函数体的二进制代码。
程序示例:
1 | func (int a,char b) //传入的a,b在栈区 |
三、内存简单估算
1.图片内存:在iOS系统中,一个APP运行时(一般占用几十兆内存),会加载APP的图标或者一些其他图片。这些图片在APP运行过程中个会一般会常驻内存,如果在程序运行过程中执行某个方法继续加载大量图片,这时内存占用急剧会上升。一般来说:
图片占用内存大小 = 长 宽 4
一张512512图片占用内存大小 = 521 512 * 4 = 1M
在实际的项目开发中,我的加载的图标大小一般为几k或者几十k,可以接受
在iOS中,图片会被自动处理为2的N次方大小,所以512 *1028
的图片和1024*1024
的图片占用内存大小一样。如果图片由[UIImage imageNamed:@"imgName"]
方法加载,则内存占用大小如上所述计算;如果图片由CALayer
或者其他类绘制,则大小加倍,因为图片由系统渲染,需要额外消耗内存。
2.程序内存: 在程序启动后,每个函数的执行都要向系统索要资源,这个索要的资源即栈空间。函数执行的局部变量和参数均存放于此。以一个长度为10^5的字符串为例,简单的估算内存:
char 类型占一个字节。
size = 10^5/1024 = 97k //可以接受
一般情况下,栈允许申请空间为2M。当函数执行所需空间大于2M时,系统会报栈溢出的异常。所以在写程序时,应尽量减少大量数据空间的开辟。
另外,很多函数的参数为指针。一个指针的大小与指针的数据类型无关,指针大小是由操作系统决定的(32位系统4个字节,64位系统8个字节),所以无论是一个NString
类型的指针,还是一个struct
指针,占用内存都一样,而且很小。当一个函数所传参数大小大于8个字节时,最好传指针,这样在函数运行时,会开辟少量栈空间,这也是为什么使用指针比较快的原因之一,因为它只传地址。
3.数据文件内存:与所加载的数据文件大小有关。
内存是系统的宝贵资源,无论是申请还是释放,都需要谨慎处理。在iOS系统中,一个APP一般情况下运行内存应小于70M。尽量避免大量动态开辟内存。例如你的程序正在使用40M内存,然后在执行方法时又动态申请了80M内存,那你的程序会立刻被系统kill。关于内存如何优化,后面会介绍几种方式。
欢迎大家对以上内容勘误。