iOS面试之内存管理大全

内存管理内容如下:

  • 内存布局
  • 内存管理
  • 数据结构
  • ARC/MRC
  • 引用计数
  • 弱引用
  • 自动释放池
  • 循环引用
image

1.内存布局

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

推荐阅读

iOS开发——最新 BAT面试题合集(持续更新中)

image
- 最上方是内核区的内存,最下方是保留的内存空间
- 中间是程序加载的内存空间
- 由下到上是低地址到高地址
- 程序加载到内存会分为三段,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查找
image
给定值是对象内存地址,目标值是数组下标索引

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操作

  • 自动释放池数据结构
- 以栈为结点通过双向链表的形式组合而成
- 和线程一一对应

  • 双向链表
image
image
- 高地址指向低地址
- 出栈与入栈

思考
- 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?
  • 循环引用?你遇到过哪些循环引用?你是如何解决的?

文章来源于网络,如有侵权,请联系小编删除。

推荐阅读更多精彩内容

  • 1、内存布局 stack:方法调用 heap:通过alloc等分配对象 bss:未初始化的全局变量等。 data:...
    AKyS佐毅阅读 952评论 0 18
  • 内存布局 stack(栈区): 方法调用 heap(堆区):通过alloc等分配的对象 bss:未初始化的全局变量...
    Jimmy_L_Wang阅读 173评论 1 1
  • 最近给人的感觉就是哪里都想去但是哪里却又不敢去,好比在你面前摆了个毒苹果,你就那么的看着,就是不敢碰,不敢吃!!!...
    唐师兄阅读 80评论 0 1
  • 内存布局 不同内存布局区域的含义stack(栈):方法调用heap(堆):通过alloc等分配的对象bss:未初始...
    爱玩游戏的iOS菜鸟阅读 1,110评论 0 21
  • 一、iOS的内存管理方式 1、小对象的内存管理 -- Tagged Pointer 2、普通对象的内存管理 -- ...
    意一ineyee阅读 1,207评论 2 33