栈区(stack) : 由编译器自动分配并释放,一般存放函数的参数值,局部变量等; 堆区(heap) : 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收; 全局区(静态区)(static) : 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放; 文字常量区 :存放常量字符串,程序结束后由系统释放; 程序代码区 :存放函数的二进制代码;
程序启动完毕后全局区,文字常量区,程序代码区系统已经自动的分配好了,栈区和堆区是后来动态的申请使用的;
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 堆:操作系统有一个记录空闲内存地址的链表 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。 由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
申请大小的限制
栈: 栈是向低地址扩展的数据结构,是一块连续的内存的区域,栈顶的地址和栈的最大容量是系统预先规定好的,在 iOS中,主线程栈的大小是1M 堆: 堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
申请效率
栈:由系统自动分配,速度较快 堆:是由alloc分配的内存,速度比较慢,而且容易产生内存碎片,不过用起来最方便(这个是我们程序员要关注的)
存储内容
栈:变量名(不带*)相当于是指向栈区数据的指针别名,变量名可以简化程序员的工作 堆:要访问堆区中的数据,必须通过指针的方式才可以进行。
缓存方式:
栈使用的是一级缓存,它们通常都是被调用时处于存储空间中,调用完毕立即释放。 堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收的)。所以调用这些对象的速度要相对来的低一些;
指针的类型决定了访问堆中数据的方式
i. 操作系统以匿名(只记录内存地址和大小,不记录具体类型)的方式记录已经分配的内存区域 ii. 当某一内存区域不再使用时,程序需要通知操作系统回收该内存区域,从而可以保证该内存区域被其他程序再次使用,否则,该区域将永远无法被再次分配,这就是传说中的“内存泄露” iii. 如果某一区域已经被释放,仍然视图访问该区域,会提示“坏内存访问”,这就是传说中的“野指针访问”
内存对齐:
编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为对齐模数。为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到正数倍的要求,也就是将预开辟空间的首地址后移几个字节; 原则:1.前面的地址必须是后面的地址整数倍,不是就补齐; 2.整个struct的地址必须是最大字节的整数倍;
一个NSObject对象占用多少内存?
我们发现一个NSObject对象转换为C语言其实就是一个结构体,而且这个结构体只有一个成员,isa指针,而指针在64位架构中占8个字节,也就是说一个NSObject对象所占用的内存是8个字节;但是我们发现NSObject对象中还有很多方法,那这些方法不占用内存空间吗?其实类的方法等也占用内存空间,但是这些方法所占用的存储空间并不在NSObject对象中;