内存管理
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
内存管理-内存销毁步骤