iOS面试之内存管理模块

2019-11-24  本文已影响0人  木子心语

内存管理内容如下:

内存管理.png

1.内存布局

内存布局.png
- 最上方是内核区的内存,最下方是保留的内存空间
- 中间是程序加载的内存空间
- 由下到上是低地址到高地址
- 程序加载到内存会分为三段,1.未初始化数据(.bss),2.已初始化数据(.data),3.代码段(.text)
- 代码段:所写的代码在代码段内存中
- 已初始化数据,静态变量,全局变量,已经初始化
- 未初始化数据,静态变量,全局变量,未初始化
- 栈,我们定义的方法或者函数都在栈中工作的
- 栈从高地址向低地址进行扩展
- 创建的对象,block经过copy放在堆上工作
- 堆是向上增长

2.内存管理

- TaggedPointer
- NONPOINTER_ISA
- 散列表
64位架构,非指针类型
NONPOINTER_ISA 1.png
0-15位
- indexed:如果是0,代表当前对象的类对象地址;如果是1,不仅是类对象的
地址,还有内存管理方面的数据,非指针型的isa
- has_assoc :当前对象是否有关联对象.如果是0,代表没有,1代表有.
- has_cxx_dtor 是否使用到c++
- shiftcls: 当前对象的类对象的指针地址
NONPOINTER_ISA 2.png
16-31位
NONPOINTER_ISA 3.png
32-47位
- magic
- weakly_referenced 弱引用指针
- deallocating 当前是否正在dealloc操作
- has_sidetable_rc 当前isa指针当中,存储的引用计数已经达到了上线的
话,需要外挂一个sidetable_rc的数据结构,去存储相关的引用计数内容
- extra_rc 额外的引用计数,当我们引用计数在很小的方位内,存在isa指针当中
NONPOINTER_ISA4.png
48-63位
SideTables()结构
SideTables.png
SideTables其实是一个哈希表
SideTables()结构.png
SideTable:
- spinlock_t 自旋锁
- RefcountMap 引用计数表
- weak_table_t 弱引用表

思考- 为什么不是一个SideTable?而是多个SideTable

思考- 怎样实现快速分流?

SideTables的本质是一张Hash表
对象指针(Key) - > (Hash函数) -> SideTable(Value)
Hash.png
给定值是对象内存地址,目标值是数组下标索引

f(ptr) = (uintptr_t)ptr % array.count

通过hash查找,提高效率

3.数据结构

- 忙等的锁
- 轻量访问
- hash表
- 通过指针,可以找到对应对象的引用计数
- 传入对象伪装操作,获取对应的引用计数
- 存储一个对象的应用计数,通过DisguisedPtr函数计算存储位置
- 获取对象引用计数值得时候,仍是通过DisguisedPtr函数计算索引的位置
- 插入与获取都是通过一个同一个函数获取位置,避免了循环遍历
- hash查找可以提高查找效率

思考- 引用计数表是通过什么实现?Hash表

weak_table_t也是一张哈希表
弱引用表.png
- 对象指针,通过Hash函数,计算对应弱引用的对象存储位置
- weak_entry_t是一个结构体数组

4.ARC/MRC

- 手动引用计数
- alloc retain release
- retainCount autorelease dealloc
- 自动引用计数
- 编译器与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实现
dealloc实现流程.png
object_dispose()实现
object_dispose()实现.png
objc_destructInstance()实现
objc_destructInstance()实现.png
clearDeallocating()实现
clearDeallocating()实现.png

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{}改写为:
void *ctx = objc_autoreleasePoolPush();
{}中的代码
objc_autoreleasePoolPop(ctx);
void *objc_autoreleasePoolPush(void)
|
void *AutoreleasePoolPage::push(void)
void objc_autoreleasePoolPop(void* ctxt)
|
AutoreleasePoolPage::pop(void* ctxt)
一次pop实际上一次批量的pop操作
- 以栈为结点通过双向链表的形式组合而成
- 和线程一一对应
双向链表.png 栈结构.png
- 高地址指向低地址
- 出栈与入栈
思考
- AutoreleasePool的实现原理是怎样的?
- AutoreleasePool为何可以嵌套使用?
应用场景:在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasepool

8.循环引用

- 自循环引用
- 相互循环引用
- 多循环引用
- 有一个对象,对象中有一个成员变量obj,强持有这个成员变量,此成员变
量赋值为元对象,就造成了自循环引用
- 对象A,obj
- 对象B,obj
- 对象A中的obj指向B
- 对象B中的obj指向A
- 此时造成相互循环引用
每一个对象的obj,都指向下一个对象,就产生了多循环引用
思考-如何解决循环引用?
- 避免产生循环引用
- 在合适的时机手动断开循环引用
具体的解决方案有哪些?
_ _ weak
对象A: id _ _ weak obj
对象B: id _ _ strong obj
对象B强持有A,对象A弱引用B

_ _ block
MRC下,_ _block修饰对象不会增加其引用计数,避免了循环引用
ARC下,_ _block修饰对象会被强引用,无法避免循环引用,需手动解除循环引用

_ _ unsafe_unretained
修饰对象不会增加其引用计数,避免了循环引用
如果被修饰对象在某一时机被释放,会产生悬垂指针

思考-面试题

QQ交流群: 796142709

上一篇下一篇

猜你喜欢

热点阅读