洋仔的博客 洋仔的博客
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

洋仔

奋斗的小青年
首页
  • 个人心法总结

    • 价值心法
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • iOS基础知识
  • 前端
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 投资体系
  • 毛选
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • iOS基础知识

    • iOS底层相关

    • Runloop系列

    • Runtime系列

    • 内存管理系列

      • weak的实现原理
      • 内存管理系列
      • autoReleasePool
        • autorelease的本质
        • autoreleasePoolPage的结构
        • autoreleasePool的结构和工作原理
          • 工作流程
        • autorelaeasepool、NSRunLoop 、子线程三者的关系
        • 如何实现autorealeasepool
        • 相关问题
          • ARC 下什么样的对象由 Autoreleasepool 管理
          • 子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
          • 子线程是否有自动释放池呢
          • 这个obj究竟在什么时候释放呢? 是在子线程销毁后释放,还是与子线程的生命周期无关?
          • 什么时候需要自己手动创建autorelease pool
          • 如果一个对象释放前被加到了NotificationCenter中,不在NotificationCenter中,那么remove对象可能会怎样
          • 关联对象的应用?系统如何实现关联对象的
          • objc中向一个nil对象发送消息将会发生什么?
      • 循环引用
      • 内存管理自我总结
    • Block系列

    • 线程系列

    • KVC跟KVO系列以及通知中心

    • UI系列

    • 离屏渲染系列

    • 组件化系列跟架构

    • OC跟webview交互系列

    • 持久化系列

    • APP编译系列

    • APP性能优化系列

    • cocoapods系列

    • swift系列

    • Git系列

    • 网络相关

    • 三方库系列

    • 系统原理

    • 总结系列

    • 算法系列

    • 数据结构系列

  • 前端

  • 技术
  • iOS基础知识
  • 内存管理系列
洋仔
2023-09-27
目录

autoReleasePool

# autorelease的本质

autorelease本质就是延迟调用release方法 MRC环境下,通过[obj autorelease]来延迟内存的释放 ARC环境下,是不能手动调用,系统会自动给对象添加autorelease

# autoreleasePoolPage的结构

class AutoreleasePoolPage 
{
    PAGE_MAX_SIZE;//最大size 4096字节
    magic_t const magic; //用来校验AutoreleasePoolPage的结构是否完整
    id *next;//指向下一个即将产生的autoreleased对象的存放位置(当next == begin()时,表示AutoreleasePoolPage为空;当next == end()时,表示AutoreleasePoolPage已满
    pthread_t const thread;//指向当前线程,一个AutoreleasePoolPage只会对应一个线程,但一个线程可以对应多个AutoreleasePoolPage;
    AutoreleasePoolPage * const parent;//指向父结点,第一个结点的 parent 值为 nil;
    AutoreleasePoolPage *child;//指向子结点,最后一个结点的 child 值为 nil;
    uint32_t const depth;//代表深度,第一个page的depth为0,往后每递增一个page,depth会加1;
}
1
2
3
4
5
6
7
8
9
10

1.AutoreleasePoolPage 本质是这么一个节点对象,大小是4096字(PAGE_MAX_SIZE:4096)。

2.前7个变量都是8字节,所以总共占用56个字节,剩下的4040字节存储着autorelease对象地址

提出个小小疑问,大家有没有想过,为什么每个AutoreleasePoolPage的大小设置成4096个字节呢? 因为4096是虚拟内存一页的大小

# autoreleasePool的结构和工作原理

autoreleasepool本质上就是一个指针堆栈,内部结构是由若干个以AutoreleasePoolPage对象为结点的双向链表组成,系统会在需要的时候动态地增加或删除page节点,如下图即为AutoreleasePoolPage组成的双向链表:

# 工作流程

  1. 在运行循环开始前,系统会自动创建一个autoreleasepool(一个autoreleasepool会存在多个AutoreleasePoolPage),此时会调用一次objc_autoreleasePoolPush函数,runtime会向当前的AutoreleasePoolPage中add进一个POOL_BOUNDARY(哨兵对象),代表autoreleasepool的起始边界地址),并返回此哨兵对象的内存地址。

  2. 这时候next指针则会指向POOL_BOUNDARY(哨兵对象)后面的地址(对象地址1)。

  3. 后面我们创建对象,如果对象调用了autorelease方法(ARC编译器会给对象自动插入autorelease),则会被添加进AutoreleasePoolPage中,位置是在next指针指向的位置,如上面next指向的是对象地址1,这是后添加的对象地址就在对象地址1这里,然后next就会 指向到对象地址2 ,以此类推,每添加一个地址就会向前移动一次,直到指向end()表示已存满。

  4. 当不断的创建对象时,AutoreleasePoolPage不断存储对象地址,直到存满后,则又会创建一个新的AutoreleasePoolPage,使用child指针和parent指针指向下一个和上一个page,从而形成一个双向链表,对象地址存储的顺序如图所示。

  5. 当调用objc_autoreleasePoolPop(哨兵对象地址)时(调用时机后面说),假设我们如上图,添加最后一个对象地址8,那么这时候就会依次由对象地址8 -> 对象地址1,每个对象都会调用release方法释放,直到遇到哨兵对象地址为止。

_objc_autoreleasePoolPush()和 _objc_autoreleasePoolPop的调用时机

App启动后主线程的RunLoop注册两个observer:_warpRunLoopWithAutoreleasePoolHandle()
第一个observer监听事件Entry,回调调用_objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个observer监听两个事件:
1. Before waiting时调用_objc_autoreleasePoolPop和push清空自动释放池。
2. Exit时调用_objc_autoreleasePoolPop释放清空。
优先级最低,保证其释放池子发生在其他所有回调之后。

# autorelaeasepool、NSRunLoop 、子线程三者的关系

  • 主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理。

  • 子线程默认不开启runloop,当产生autorelease对象时候,会将对象添加到最近一次创建的autoreleasepool中,一般是main函数中的autoreleasepool,由主线程runloop管理;也就是不用手动创建Autoreleasepool,线程销毁时在会在最近一次创建的autoreleasepool 中释放对象。

  • 自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。

  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程),每开一个线程,会有与之对应的AutoreleasePool。

# 如何实现autorealeasepool

autorealeasepool(自动释放池)其实并没有其自身的结构,它是基于多个Autorelease PoolPage(一个C++类)以双向链表组合起来的结构,其基本操作都是简单封装了AutoreleasePoolPage的操作方法。例如,可以通过push操作添加对象,或者通过pop操作弹出对象,以及通过release操作释放销毁对象,对应的3个封装后的操作函数为:objc_autoreleasepoolPush、objc_autoreleasepoolPop和objc_autorelease。自动释放池将用完的对象集中起来,统一释放,起到延迟释放对象的作用。

自动释放池存储于内存中的栈上,释放池之间遵循“先进后出”原则。例如下面代码所示的释放池嵌套。

提示

/* 释放池 1*/
@autoreleasepool {
    People *person1 = [[[Person alloc] init] autorelease];
    /* 释放池 2*/
    @autoreleasepool {
        People *person2 = [[[Person alloc] init] autorelease];
    }
    People *person3 = [[[Person alloc] init] autorelease];
}
1
2
3
4
5
6
7
8
9

代码中释放池1和释放池2在内存中的结构如图所示,释放池1先入栈,后出栈;释放池2后入栈,先出栈。person2对象在释放池2中,会被先释放;person1和person3在释放池1中,会后被释放。

Screenshot-2023-08-26-at-22

常见真题:下面这段代码有什么问题?如何修改?

for (int i = 0; i < someLargeNumber; i++) {
    NSString *string = @"Abc";
    string = [string lowercaseString];
    string = [string stringByAppendingString:@"xyz"];
    NSLog(@"%@",string);
}
1
2
3
4
5
6

答案:代码通过循环短时间内创建了大量的NSString对象,在默认的自动释放池释放之前这些对象无法被立即释放,会占用大量内存,造成内存高峰以致内存不足。 为了防止大量对象堆积应该在循环内手动添加自动释放池,这样在每一次循环结束,循环内的自动释放池都会被自动释放及时腾出内存,从而大大缩短了循环内对象的生命周期,避免内存占用高峰。 代码改进方法是在循环内部嵌套一个自动释放池:

for (int i = 0; i < 1000000; i++) {
    @autoreleasepool {
        NSString *string = @"Abc";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"xyz"];
        NSLog(@"%@",string);
    }
}
1
2
3
4
5
6
7
8

# 相关问题

# ARC 下什么样的对象由 Autoreleasepool 管理

__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象可能被废弃。那么如果把对象注册到autorealeasepool中,那么在@autorealeasepool块结束之前都能确保对象的存在。最新的情况是weak修饰的对象不会再被加入到Pool了

# 子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?

在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。

# 子线程是否有自动释放池呢

在子线程中原本是没有自动释放池的,但是如果有runloop或者autorelease对象的时候,就会自动的创建自动释放池。

# 这个obj究竟在什么时候释放呢? 是在子线程销毁后释放,还是与子线程的生命周期无关?

每个autorelease创建的时候都会监听当前线程的销毁方法,在线程退出时调用tls_dealloc方法。

# 什么时候需要自己手动创建autorelease pool

你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值。

解析

该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,在循环中创建自己的autoReleasePool,能够及时释放占用内存大的临时变量,减少内存占用峰值

我们经常会在一些第三方库中看到这种情况,线程的入口加上Autorelease Pool,这是为何?

一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。

在默认情况下线程是joinable,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。

由此可知,线程占用的资源要释放的前提是线程终止,如果加了autoreleasepool相关对象会在pool执行完毕后释放,避免过多的延迟释放造成程序占用过多的内存。

# 如果一个对象释放前被加到了NotificationCenter中,不在NotificationCenter中,那么remove对象可能会怎样

前面已经讲到对于NotificationCenter的使用,只要添加对象到消息中心进行通知注册,之后就一定要对其remove进行通知注销。将对象添加到消息中心后,消息中心只是保存该对象的地址,消息中心到时候会根据地址发送通知给该对象,但并没有取得该对象的强引用,对象的引用计数不会加1。如果对象释放后没有从消息中心remove,也就是通知中心还保存着那个指针,而那个指针指的对象可能已经被释放销毁了,那么那个指针就成为一个野指针,当通知发生时,会向这个野指针发送消息导致程序崩溃。

# 关联对象的应用?系统如何实现关联对象的

应用: 可以在不改变类的源码的情况下,为类添加实例变量(注意:这里指的实例变量,并不是真正的属于类的实例变量,而是一个关联值变量) 结合category使用,为类扩展存储属性。 关联对象实现原理:

系统通过管理一个全局哈希表,通过对象指针地址和传递的固定参数地址来获取关联对象。根据setter传入的参数协议,来管理对象的生命周期。

关联对象的值实际上是通过AssociationsManager对象负责管理的,这个对象里有个AssociationsHashMap静态表,用来存储对象的关联值的,关于AssociationsHashMap存储的数据结构如下:

AssociationsHashMap:

------添加属性对象的指针地址(key):ObjectAssociationMap(value:所有关联值对象)

ObjectAssociationMap:

------关联值的key:关联值的value

# objc中向一个nil对象发送消息将会发生什么?

如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。

详解: 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil); 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型标量,发送给nil的消息将返回0; 如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0; 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的

编辑 (opens new window)
上次更新: 2024/10/23, 23:26:17
内存管理系列
循环引用

← 内存管理系列 循环引用→

最近更新
01
数组
10-25
02
数组双指针系列之对撞指针
10-25
03
数组双指针系列之快慢指针
10-25
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式