内存管理面试题

autorelease 对象会在什么时候释放?

  • 使用@autoreleasepool

会在大括号结束时释放

 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }

    ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

    void * atautoreleasepoolobj;
 };

{ } 是作用域,
{ } 里会调用构造函数间接的会调objc_autoreleasePoolPush函数:
{ } 外调用 析构函数调用objc_autoreleasePoolPop函数

  • 不使用 @autoreleasepool

这个会由系统自动释放,释放时机是在当前 runloop 结束时释放,因为系统会自动为每个
runloop 执行自动释放池的 push 和 pop 操作

Runloop和Autorelease

iOS在主线程的Runloop中注册了2个Observer

  • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer <1> 监听了kCFRunLoopBeforeWaiting事件,会调用
    objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
  • <2> 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

什么对象会加入Autoreleasepool中

  • 使用alloc、new、copy、mutableCopy的方法进行初始化时,由系统管理对象,在适当的位置release。
  • 使用array会自动将返回值的对象注册到Autoreleasepool。
  • __weak修饰的对象,为了保证在引用时不被废弃,会注册到Autoreleasepool中。
  • id的指针或对象的指针,在没有显示指定时会被注册到Autoleasepool中。

谈谈你对自动释放池的理解

自动释放池是oc提供的一种自动回收的机制,具有延迟释放的特性,即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出{}或者超出[pool release]之后再被释放。

系统就是通过@autoreleasepool {}这种方式来为我们创建自动释放池的,
一个线程对应一个runloop,系统会为每一个runloop隐式的创建一个自动释放池,
所有的autoreleasePool构成一个栈式结构,在每个runloop结束时,
当前栈顶的autoreleasePool会被销毁,而且会对其中的每一个对象做一次release(严格来说,是你对这个对象做了几次autorelease就会做几次release,不一定是一次).

特别指出,使用容器的block版本的枚举器的时候,系统会自动添加一个autoreleasePool

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];

自动释放池的原理

AutoreleasePoolPage:每一个自动释放池没有单独的结构,每一个autorealeasePool对象都是由若干个autoreleasePoolPage通过双向链表连接而成,类的定义如下

class AutoreleasePoolPage
{
    magic_t const magic;
    id *next;//指向栈顶最新被添加进来的autorelease对象的下一个位置
    pthread_t const thread;//指向当前线程
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t
  }
  • autoreleasePool是按照线程一一对应的,
  • autoreleasePoolPage会开辟4096字节空间,除了上面的实例变量所占的空间,剩余的空间全部用来存储autorelease对象的地址
  • id *next:指向栈顶最新被添加进来的autorelease对象的下一个位置
  • 一个autoreleasePoolPage空间被占满时,会创建一个新的autoreleasePoolPage对象,后来的对象添加在在新
    autoreleasePoolPage中
  • 哨兵对象:本质是一个值为nil的对象,由被定义在* NSAutoreleasePool类中的_token指针来保存。
    hotPage:最新创建的AutoreleasePoolPage.

系统通过一个栈来管理所有的自动释放池,每当创建了一个新的自动释放池,系统就会把它压入栈顶,并且传入一个哨兵对象,将哨兵对象插入hotPage,这里分三种情况

  • 若hotPage未满,则直接插入哨兵对象,
  • 要是满了,新建一个NSAutoreleasePoolPage,并将其作为hotPage,然后将哨兵对象插入
  • 如果没有NSAutoreleasePoolPage,则新建一个NSAutoreleasePoolPage,并将其作为hotPage,插入哨兵对象,注意。这里的hotPage是没有父节点的。

每当有一个自动释放池要被释放的时候,哨兵对象就会作为参数被传入,找到该哨兵对象所在的位置后,将所有晚于哨兵对象的autorelease弹出,并对他们做一次release,然后将next指针一到合适的位置。

自动释放池的应用场景

  • 对象作为函数返回值

当一个对象要作为函数返回值的时候,因为要遵循谁申请谁释放的思想,所以应该在返回之前释放,但要是返回之前释放了,就会造成野指针错误,但是要是不释放,那么就违背了谁申请谁释放的原则,所以就可以使用autorelease延迟释放的特性,将其在返回之前做一次autorelease,加入到自动释放池中,保证可以被返回,一次runloop之>>后系统会帮我们释放他

  • 临时生成大量对象,一定要将自动释放池放在for循环里面,要释放在外面,就会因为大量对象得不到及时释放,而造成内存紧张,最后程序意外退出

介绍下内存的几大区域?

栈区(stack)

编译器自动分配并释放存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活[先进后出]

堆区(heap)

由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。

优点是灵活方便,数据适应面广泛,但是效率有一定降低。

全局区(静态区)

(static) 全局变量和静态变量的存储是放在一起的,
初始化的全局变量和静态变量存放在一块区域,
未初始化的全局变量和静态变量在相邻的另一块区域,
程序结束后有系统释放。

文字常量区

存放常量字符串,程序结束后由系统释放;

代码区

存放函数的二进制代码

请添加图片描述
请添加图片描述
栈区 (stack [stæk]): 由编译器自动分配释放

  • 局部变量是保存在栈区的
  • 方法调用的实参也是保存在栈区的

堆区 (heap [hiːp]): 由程序员分配释放

代码段:程序结束后由系统释放

程序编译链接 后的二进制可执行代码

申请后的系统是内存如何响应的?

栈:存储每一个函数在执行的时候都会向操作系统索要资源,栈区就是函数运行时的内存栈区中的变量由编译器负责分配和释放,内存随着函数的运行分配,随着函数的结束而释放,由系统自动完成

注意:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:

  • 首先应该知道操作系统有一个记录空闲内存地址的链表。
  • 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值