内存管理内容如下:
- 内存布局
- 内存管理
- 数据结构
- ARC/MRC
- 引用计数
- 弱引用
- 自动释放池
- 循环引用
1.内存布局
image作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:638302184,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 与2800+iOS开发者一起交流学习成长!
- 最上方是内核区的内存,最下方是保留的内存空间
- 中间是程序加载的内存空间
- 由下到上是低地址到高地址
- 程序加载到内存会分为三段,1.未初始化数据(.bss),2.已初始化数据(.data),3.代码段(.text)
- 代码段:所写的代码在代码段内存中
- 已初始化数据,静态变量,全局变量,已经初始化
- 未初始化数据,静态变量,全局变量,未初始化
- 栈,我们定义的方法或者函数都在栈中工作的
- 栈从高地址向低地址进行扩展
- 创建的对象,block经过copy放在堆上工作
- 堆是向上增长
- stack:(栈区):方法调用
- heap:通过alloc等分配的对象
- bss:未出化的全局变量等
- data:已初始化的全局变量等
- text:程序代码
2.内存管理
- TaggedPointer
- NONPOINTER_ISA
- 散列表
- NONPOINTER_ISA
64位架构,非指针类型
image
0-15位
- indexed:如果是0,代表当前对象的类对象地址;如果是1,不仅是类对象的
地址,还有内存管理方面的数据,非指针型的isa
- has_assoc :当前对象是否有关联对象.如果是0,代表没有,1代表有.
- has_cxx_dtor 是否使用到c++
- shiftcls: 当前对象的类对象的指针地址
image
16-31位
image
32-47位
- magic
- weakly_referenced 弱引用指针
- deallocating 当前是否正在dealloc操作
- has_sidetable_rc 当前isa指针当中,存储的引用计数已经达到了上线的
话,需要外挂一个sidetable_rc的数据结构,去存储相关的引用计数内容
- extra_rc 额外的引用计数,当我们引用计数在很小的方位内,存在isa指针当中
image
48-63位
- 散列表
SideTables()结构
image
SideTables其实是一个哈希表
image
SideTable:
- spinlock_t 自旋锁
- RefcountMap 引用计数表
- weak_table_t 弱引用表
思考- 为什么不是一个SideTable?而是多个SideTable
思考- 怎样实现快速分流?
SideTables的本质是一张Hash表
对象指针(Key) - > (Hash函数) -> SideTable(Value)
- Hash查找
给定值是对象内存地址,目标值是数组下标索引
f(ptr) = (uintptr_t)ptr % array.count
通过hash查找,提高效率
3.数据结构
- 自旋锁
- 忙等的锁
- 轻量访问
-
引用计数表
image
- hash表
- 通过指针,可以找到对应对象的引用计数
- 传入对象伪装操作,获取对应的引用计数
- 存储一个对象的应用计数,通过DisguisedPtr函数计算存储位置
- 获取对象引用计数值得时候,仍是通过DisguisedPtr函数计算索引的位置
- 插入与获取都是通过一个同一个函数获取位置,避免了循环遍历
- hash查找可以提高查找效率
思考- 引用计数表是通过什么实现?Hash表
- 弱引用表
weak_table_t也是一张哈希表
image
- 对象指针,通过Hash函数,计算对应弱引用的对象存储位置
- weak_entry_t是一个结构体数组
4.ARC/MRC
- MRC
- 手动引用计数
- alloc retain release
- retainCount autorelease dealloc
- ARC
- 自动引用计数
- 编译器与runtime协作的结果
- 禁止手动调用retain/release/retainCount/dealloc
- 新增weak,strong属性关键字
5.引用计数管理
- 实现原理分析
alloc实现
- 经过一系列调用,最终调用了C函数calloc
- 此时并没有设置引用计数为1
retiain实现
SideTable & table = SideTables()[this];
size_t & refcntStorage = table.refcnts[this];
refcntStorage += SIDE_TABLE_RC_ONE;
思考,retain操作,系统怎样查找引用计数的?经过两次hash查找
release实现
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;
retainCount实现
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
RefcountMap::iterator it = table.refcnts.find(this);
refcnt_result += it->second >> SIDE-TABLE_RC_SHIFT;
dealloc实现
image
object_dispose()实现
image
objc_destructInstance()实现
image
clearDeallocating()实现
image
6.弱引用
{
id __weak obj_new = obj;
}
|
编译
|
{
id obj_new;
objc_initWeak(&obj_new,obj);
}
objc_initWeak()
|
storeWeak()
|
weak_register_no_lock()
清除weak变量,同时设置指向为nil
dealloc---...--- weak_clear_no_lock()
7.自动释放池
- AutoreleasePool
编译器将@autoreleasepool{}改写为:
void *ctx = objc_autoreleasePoolPush();
{}中的代码
objc_autoreleasePoolPop(ctx);
- objc_autoreleasePoolPush
void *objc_autoreleasePoolPush(void)
|
void *AutoreleasePoolPage::push(void)
- objc_autoreleasePoolPop
void objc_autoreleasePoolPop(void* ctxt)
|
AutoreleasePoolPage::pop(void* ctxt)
一次pop实际上一次批量的pop操作
- 自动释放池数据结构
- 以栈为结点通过双向链表的形式组合而成
- 和线程一一对应
- 双向链表
- 栈
- 高地址指向低地址
- 出栈与入栈
思考
- AutoreleasePool的实现原理是怎样的?
- AutoreleasePool为何可以嵌套使用?
应用场景:在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasepool
8.循环引用
- 自循环引用
- 相互循环引用
- 多循环引用
-
自循环引用
image
- 有一个对象,对象中有一个成员变量obj,强持有这个成员变量,此成员变
量赋值为元对象,就造成了自循环引用
-
相互循环引用
image
- 对象A,obj
- 对象B,obj
- 对象A中的obj指向B
- 对象B中的obj指向A
- 此时造成相互循环引用
-
多循环引用
image
每一个对象的obj,都指向下一个对象,就产生了多循环引用
思考-如何解决循环引用?
- 避免产生循环引用
- 在合适的时机手动断开循环引用
具体的解决方案有哪些?
_ _ weak
对象A: id _ _ weak obj
对象B: id _ _ strong obj
对象B强持有A,对象A弱引用B
_ _ block
MRC下,_ _block修饰对象不会增加其引用计数,避免了循环引用
ARC下,_ _block修饰对象会被强引用,无法避免循环引用,需手动解除循环引用
_ _ unsafe_unretained
修饰对象不会增加其引用计数,避免了循环引用
如果被修饰对象在某一时机被释放,会产生悬垂指针
思考-面试题
- 什么是ARC?
- 为什么weak指针指向的对象在废弃之后会被自动置为nil?
- 如何实现AutoreleasePool?
- 循环引用?你遇到过哪些循环引用?你是如何解决的?
文章来源于网络,如有侵权,请联系小编删除。
网友评论