内存管理 RAM RAM :运行内存,CPU可以直接访问,读写速度非常快,但是不能掉电存储 1. 动态DRAM 速度慢一点,需要定期的刷新(充电),我们常说的内存条就是指它,价格会稍低 一点,手机中的运行内存也是指它 2. 静态SRAM 速度快,我们常说的一级缓存,二级缓存就是指它,当然价格高一点 RAM类型不具备掉电存储能力(即一停止供电数据全没了,从新上电后全是乱 码,所以需要初始化) app程序一般存放于ROM中 ROM ROM :存储性内存,可以掉电存储,例如SD卡、Flash(机械磁盘也可以简单的 理解为ROM) 手机里面使用的ROM基本都是NandFlash, CPU不可以直接访问 需要文件系统/驱动程序(嵌入式中的EMC)将其读到RAM里面,CPU才可以访问 地址从 高->低 : 栈区->堆区->全局区->常量区->代码区 内存分区 (RAM) 栈区 stack 高地址 由系统管理,会自动存一些局部变量,函数跳转跳转时现场保护(寄存器值保存于 恢复) 局部变量,递归,函数循环调用 等等 非手动作用区域 堆区 heap 程序员管理,比如alloc申请内存,free释放内存 全局区静态区 static 全局变量和静态变量的存储区域 1. bss段 未初始化的全局变量和静态变量 2. 数据段 已经初始化的全局变量和静态变量 已初始化(int a = 10;)的全局变量和静态变量在一块区域, 未初始化(int a;)的全局变量和未初始化的静态变量在相邻的另一块区域 程序员创建,由系统系统释放 常量区 常量字符串就是放在这里的,还有const常量 代码区 低地址 app程序会拷贝到这里 实际上不是一次性把数据复制到真实的物理内存中, 而是一开始在虚拟内存中 系统把ROM里的APP部分加载代码(比如动态库,类方法属性等等)copy到RAM 的代码区, 这样运行时CPU就能直接访问相关代码了 虚拟内存与物理内存 物理内存(RAM) 真实的物理内存一般较小,比如2G 虚拟内存 (ROM上) 在copy代码时不是真的一下全部复制到RAM上,而是在ROM硬盘上规划出一个 大小为4G(32位的=2的32次方)空间当做虚拟内存, 因为CPU并不能直接访问 ROM,所有虚拟内存地址都对应页表项(建立好虚拟内存和磁盘文件之间的映 射) CPU访问到虚拟页有效位为1,说明已经在物理内存中,如果是0 :找到对应磁盘 位置,再复制到ARM内存中(达到一个过渡作用) isa 每个对象都有一个isa,它不再是简单的一个指针,64位操作系统后,isa优化成 了共用体(存储多种信息,包括引用计数) isa定义: define ISA_BITFIELD uintptr_t nonpointer : 1; //指针是否优化过 uintptr_t has_assoc : 1; //是否有设置过关联对象,如果没有,释放时 会更快 uintptr_t has_cxx_dtor : 1; //是否有C++的析构函数 (.cxx_destruct),如果没有,释放时会更快 uintptr_t shiftcls : 33; //存储着Class、Meta-Class对象的内存地址信 uintptr_t magic : 6; //用于在调试时分辨对象是否未完成初始化 uintptr_t weakly_referenced : 1; //是否有被弱引用指向过,如果没有,释放 时会更快 uintptr_t deallocating : 1; //对象是否正在释放 uintptr_t has_sidetable_rc : 1; //引用计数器是否过大无法存储在isa中 uintptr_t extra_rc : 19 //里面存储的值是引用计数器减1 通过上面isa结构发现,内部一共64位,分别记录不同信息 ,isa记录者 关联, 析构,类信息(33位),弱引用 释放状态,引用计数 (1+19位)等等,不是简 单 的指针 系统也是根据相应状态进行下一步处理 引用计数超出范围时,部分计数移动到引用计数表(SideTables)里存储 引用计数表 SideTables SideTables是全局散列表,包含64个结构体 每个结构体包含一个引用计数表和一个弱引用计数表 项目一共64个引用计数表和64个弱引用计数表 引用计数表 ? 也是散列表(哈希) 存储的是字典: Key 是对象地址 value 是计数值 弱引用计数表 ? 也是散列表(哈希) 存储的是结构体 对象地址 对象的弱引用指针数组 对象 小对象(Tagged Pointer ) : 64位开始:当NSNumber,NSString,NSData等值范围较小时存储Tagged Pointer 类型 大小 :8个字节存储就可以了 类型:tag类型 + data数据 标识 释放:随着指针释放 扩容:当值超出范围后,变为普通对象类型 方法:调用方法时也是有区别的,不像正常msgsend流程 普通对象: iOS普通对象是通过isa(引用计数标记方式)来管理内存的 0 释放 > 0 持有 上面isa结构显示引用计数 19位存储引用计数:范围0~255 1位标记是否超出范围 :0,1 假如标记=1,说明超出,此时就把引用计数设置为128,另外的128个计数挪到 引用计数表(SideTables)里,下次计数加一,还是在isa里增加,直到溢出,如 此循环 引用计数最终调用objc的retain和release来操作: inline id objc_object::rootRetain() { assert(!UseGC); //是小对象,不使用引用计数,直接返回,免去相关内存管理,性能高了很多 if (isTaggedPointer()) return (id)this; return sidetable_retain(); //普通对象,计数+1 } inline bool objc_object::rootRelease() { assert(!UseGC); if (isTaggedPointer()) return false; //释放 return sidetable_release(true); //释放 } 当进行alloc,new,retain等引用计数+1 当进行release,autorelease时都会进行计数-1或者dealloc,清空 ARC ? ARC是相对MRC来说的 MRC 程序员手动管理引用计数,一一对应, retain了一次,最终都要release一次,这 样才能释放 ARC干了啥 系统帮我们管理了部分引用计数, 比如使用了alloc或者retain,无需手动 release,超出作用域,或者最后再dealloc里会进行释放,也就是系统帮我们在 合适的地方进行了release _ _weak 弱引用,记录在弱引用表里的对象的弱引用指针数组中 Block内部常使用,避免循环引用 计数=0时,系统自动把弱引用设nil,并清空所有的关联 _ _strong 作用域强引用: 作用域内系统会retain计数+1 超出作用域后,会release计数-1 Block内部常使用,避免循环引用, 同时避免weak修饰对象释放导致问题,延迟 对象释放,等block执行完毕后再释放 atomic 原子性: 也只是对set、get进行加锁,其他方法依然不是线程安全的 因此,通常情况下还是要进行额外加锁 一般使用nonatomic 自己根据需要加锁 读写 对内存数据可读read可写write能力 内存管理-对象生成alloc 内存管理-对象销毁dealloc 内存管理-内存销毁步骤