内存管理

一、内存布局
内核区-》栈(stack)-》堆(heap)-》未初始化数据(.bss)-》已初始化数据(.data)-》代码段(.text)-》保留

在ios中定义的方法或者函数都是在内存的栈区进行工作的,栈是由高地址向低地址。
创建的对象或者block进行copy之后会转移到堆上去,堆的内存地址是由低地址向高地址增长

stack:方法调用
heap:通过alloc分配的对象,copy之后的block
bss:未初始化的全区变量
data:已初始化的全局变量
text:程序代码

iOS是怎么样进行管理的:小对象是TaggedPointer,64位架构下NONPOINTER_ISA,散列表(弱引用表,和应用计数表)
arm64位下的NONPOINTER_ISA上的64位0或1分表存储和表示了很多信息
side tables(散列表)是一个哈希表里面里面存储了很多side table,每个side table里存储了spinlock_t(自旋锁)、RefcountMap(引用计数)、weak_table_t(弱引用表),为什么是多个side table,是因为为了解决访问效率问题,采用了分离所。
Spinlock_t 是忙等锁,适用于轻量级的访问
RefcountMap 使用哈希表来实现的,为了提高查找效率,插入和取出是通过同一个哈希算法来决定的,避免了遍历
weak_table_t 也是一张哈希表

二、应用计数

自动引用计数ARC是LLVM(编译器)和Runtime协作的结果,编译器在编译的时候在相应的地方插入了release和autorelease,arc中禁止调动retain、release retainCount、dealloc,arc中新增了weak和strong关键字

alloc:经过一系列的调用最终调用了c函数的calloc,此时并没有设置引用计数为1
retain :通过两次的hash查找对其引用计数进行+1
release:通过hash到sedetable中查找到相应的值进行-1操作
retianCount:定义了一个局部变量1,再加上去sidetable中查出的应用计数值,所以alloc出来后的引用计数并没有设置为1
dealloc:首先要判断是否可以直接释放(nonpointer_isa,weakly_refrenced,has_assoc,has_cxx_dtor,has_table_rc)如果可以则直接调用free,如果不行则需要调用object_dispose,通过dealloc实现源码可以判断关系对象在使用后不需要手动销毁
一个对象由weak指针指向他,对象被销毁了为什么指针会置为nil,是因为对象的dealloc对若引用指针置为nil(weak_clear_no_lock)

三、自动释放
自动释放池是怎么实现的,是以栈为节点通过双向链表形式组合而成的,是和线程一一对应的,AutoreleasePoolPage就相当于每一个节点
@autoreleasepool相当于如下代码
AutoreleasePoolPage::push
{
代码片段
}
AutoreleasePoolPage::pop
在当次runloop将要结束的时候会调用AutoreleasePoolPage::pop
多层嵌套调用是多次插入哨兵报对象(next)
使用场景:在for循环中alloc图片数据等内存消耗比较大的场景中插入autoreleasePool,在每一次for循环都进行释放来防止内存占用过大

四、循环引用
自循环引用、相互循环引用(代理)、多循环引用
破解方法:避免产生循环引用,在合适的时机断开引用,__waek,__block(在mrc下不会增加引用计数,避免了循环引用,arc下修饰会被对象强引用,无法避免循环引用,需手动破解),__unsafe_unretained(修饰对象不会增加引用计数,可以避免循引用,如果被修饰对象在某一时机被释放,会产生悬垂指针不安全)

NSTimer的循环引用问题
NSTimer产生是会由当前线程的runloop强引用,如果是重复的NSTimer需要建立一个中间对象,定义一个中间对象,分别对timer和vc进行弱引用,在中间对象中进行定时器方法的调用,如果发现vc被销毁了对nstimer进行invalidate和置为nil

你可能感兴趣的:(内存管理)