【iOS】—— autoreleasePool以及总结

news2025/2/21 23:29:43

autoreleasePool以及总结

      • 1. 什么是autoreleasePool
      • 2. autoreleasePoolPage
        • objc_autoreleasePoolPush方法:
        • objc_autoreleasePoolPop方法:
          • token
          • kill()方法
      • 3. 总结
      • 3.1 autoreleasePool的原理
      • 3.2 autoreleasePool的问题
        • 3.2.1 autoreleasepool的嵌套操作
        • 3.2.2 autoreleasepool的释放时机
        • 3.2.3 那些对象可以加入到autoreleasePool中
        • 3.2.4 关于哨兵对象和next指针
        • 3.2.5 next和child:

1. 什么是autoreleasePool

AutoreleasePool(自动释放池)是在Objective-CSwift中用于管理内存释放的机制。通过创建自动释放池,可以将需要延迟释放的对象放入其中,在自动释放池被销毁时,其中的对象会被释放,从而帮助避免内存泄漏并优化内存管理。

大概的意思就是:

  • 自动释放池是栈结构,存储的是指针。
  • 指针指向需要自动释放的对象或者 POOL_BOUNDARY 边界值。以 POOL_BOUNDARY 为边界,当释放池释放时,在边界之内的对象会被释放。
  • page 以双向链表的形式构成 pool,page 会自动创建或释放。
  • 线程本地存储指向当前最新的 page。

2. autoreleasePoolPage

每个autoreleasePool都是由一系列autoreleasePoolPage组成的,并且每个autoreleasePoolPage大小都为4096字节,相关源码如下:

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES

class AutoreleasePoolPage {
    magic_t const magic;//AutoreleasePoolPage 完整性校验
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}

自动释放池其实就是一个由AutoreleasePoolPage构成的双向链表,其结构中的childparent分别指向其前趋和后继。
在这里插入图片描述

单个AutoreleasePoolPage结构如下:
在这里插入图片描述

其中有 56 bit 用于存储AutoreleasePoolPage的成员变量。

  • 该结构体的第一个成员变量是magic,我们在isa中也学习过,isa中是分判对象是否未完成初始化,在这里也一样,用来检查这个节点是否已经被初始化了。
  • begin()和end()这两个类的实例方法帮助我们快速获取 0x100816038 ~ 0x100817000 这一范围的边界地址。
  • next:指向下一个为空的内存地址,如果next指向的地址加入一个object,它就会如下图所示移动到下一个为空的内存地址中,就像栈顶指针一样。
  • thread:保存了当前页所在的线程。
  • depth:表示page的深度,首次为0,每个page的大小都是4096字节(16进制0x1000),每次初始化一个page,depth都加一。
  • POOL_BOUNDARY:就是哨兵对象,它只是nil的别名,用于分隔Autoreleasepool。POOL_BOUNDARY直译过来就是POOL的边界。它的作用是隔开page中的对象。因为并不是每次push与pop之间存进的对象都刚好占满一个page,可能会不满,可能会超过,因此这个POOL_BOUNDARY帮助我们分隔每个@autoreleasepool块之间的对象。也就是说这个page可能存储很多个@autoreleasepool块的对象,使用POOL_BOUNDARY来隔开每个@autoreleasepool块的对象。
 #define POOL_BOUNDARY nil

objc_autoreleasePoolPush方法:
 void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

这里调用了AutoreleasePoolPage::push()方法:

 static inline void *push() 
{
    id *dest;
    // POOL_BOUNDARY就是nil
    // 首先将一个哨兵对象插入到栈顶
    if (DebugPoolAllocation) {
        // 区别调试模式
        // 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}

其中调用了autoreleaseFast方法,hotPage指的是当前正在使用的AutoreleasePoolPage

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {//有 hotPage 并且当前 page 不满,将object加入当前栈中
       return page->add(obj);
   } else if (page) {//有hotPage 但当前page已满,找未满页或创建新页,将object添加到新页中
       return autoreleaseFullPage(obj, page);
   } else {//无hotPage,创建hotPage,加入其中
       return autoreleaseNoPage(obj);
   }
}
 

有hotPage但当前page未满,直接调用page->add(obj)方法将对象添加到自动释放池中。

// 这其实就是一个压栈操作,将对象加入AutoreleasePoolPage,然后移动栈顶指针
id *add(id obj) {
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}
 

有hotPage但当前page已满,找到未满页或者创建新页,将object添加到新页中autoreleaseFullPage当前page满的时候调用)

static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
//一直遍历,直到找到一个未满的 AutoreleasePoolPage,如果找到最后还没找到,就新建一个 AutoreleasePoolPage
    do {
        if (page->child) 
        	page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
	
	//将找到的,或者构建的page作为hotPage,然后将obj加入
    setHotPage(page);
    return page->add(obj);
}

无hotPage,创建hotPage,加入其中:

这个时候,由于内存中没AutoreleasePoolPage,就要从头开始构建这个自动释放池的双向链表,那么当前页表作为第一张页表,是没有parent指针的。并且我们在第一次创建page时其首位都是要加POOL_SENTINEL标识的,方便让page知道在哪就结束了。

 static id *autoreleaseNoPage(id obj) {
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // 创建AutoreleasePoolPage
    setHotPage(page); // 设置page为当前页
 
    if (obj != POOL_SENTINEL) { // 加POOL_SENTINEL哨兵
        page->add(POOL_SENTINEL);
    }
 
    return page->add(obj); // 将obj加入
}

objc_autoreleasePoolPop方法:
 void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

其调用的pop方法如下:

 static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);//使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
    id *stop = (id *)token;

    page->releaseUntil(stop);//调用 releaseUntil 方法释放栈中的对象,直到 stop 位置,stop就是传递的参数,一般为哨兵对象

	//调用 child 的 kill 方法,系统根据当前页的不同状态kill掉不同child的页面
	//releaseUntil把page里的对象进行了释放,但是page本身也会占据很多空间,所以要通过kill()来处理,释放空间
    if (page->child) {
        if (page->lessThanHalfFull()) { // 当前page小于一半满
            page->child->kill(); // 把当前页的孩子杀掉
        } else if (page->child->child) { // 否则,留下一个孩子,从孙子开始杀
            page->child->child->kill();
        }
    }
}

假设当前page一半都没满,说明剩余的page空间已经暂时够了,把多余的儿子page就可以kill掉,如果超过一半页,就认为下一半page还有存在的必要,所以kill孙子page,保留一个儿子page。

token
  • token是指向该pool的POOL_BOUNDARY指针
  • token的本质就是指向哨兵对象的指针,存储着每次push时插入的POOL_BOUNDARY的地址
  • 只有第一次push的时候会在page中插入一个POOL_BOUNDARY【或者page满了,或者没有hotPage需要使用新的page了】,并不是page的开头都一定是POOL_BOUNDARY。
kill()方法
void kill() {
    AutoreleasePoolPage *page = this; // 获取当前页
    while (page->child) page = page->child; // child存在就一直往下找,直到找到一个不存在的

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil; // 将其child指向置nil,防止出现悬垂指针
            page->protect();
        }
        delete deathptr; // 删除
    } while (deathptr != this); // 直到this处停止
}

3. 总结

3.1 autoreleasePool的原理

自动释放池本质是autoreleasePoolPage结构体对象,是一个以栈结构存储的页,每一个autoreleasePoolPage都是以双向链表的形式来连接起来。

大小可根据宏定义查得数值为:4096字节

自动释放池的出栈和入栈主要通过objc_autoreleasePoolPushobjc_autoreleasePoolPop,实际上调用的是autoreleasePoolPagepushpop方法。

**push操作:**每次调用push操作都会创建一个新的autoreleasePoolPage,而autoreleasePoolPagePush的具体操作就是插入一个POOL BOUNDARY,并且返回插入POOL BOUNDARY的内存地址。在push中的操作,需要调用autoreleaseFast方法处理,具体情况分下面三个:

  • page存在并且未满,直接调用add方法将对象添加到page的next指针处,next指针递增。
  • page存在并且已满,需要调用autoreleaseFullPage,初始化一个新的page,然后通过add方法添加。
  • page不存在,需要先调用autoreleaseNoPage,创建一个hotPage,然后调用add方法添加对象到栈中。

pop操作: 当执行pop操作的时候,会传入一个参数,这个参数是push操作的返回值,也就是POOLBOUNDARY的内存地址token,因此pop操作,就是根据token找到哨兵对象的位置,然后objc_release释放token之前的对象,把next指针指到正确的位置。

POOL_BOUNDARY是一种特殊标记,用于标记池的边界,就是说这个page可能存储很多个@autoreleasepool块的对象,在早期可能是一个特殊的指针或者整数值;在现代时,POOL_BOUNDARY可能是一个特殊的结构体或者无效指针。

整体思路:

通过push创建一个autoreleasePoolPage对象,会在开始的位置存放POOL BOUNDARY哨兵对象,然后将注册了autorelease的对象添加到存储到里面,调用pop的时候,会根据push返回的token的位置,释放到token之前的位置。

3.2 autoreleasePool的问题

3.2.1 autoreleasepool的嵌套操作

可以使用嵌套操作,其目的是控制应用程序的内存峰值,是其不要太高。

嵌套原因:自动释放池是以栈为节点,通过双向链表的形式链接,且与线程一一对应。

3.2.2 autoreleasepool的释放时机

在没有手动添加autoreleasePool的情况下,autorelease对象是在当前的runloop迭代结束的时候释放。

  1. 在准备进入runloop的时候,创建一个autoreleasePool(优先级最高)。
  2. runloop准备休眠的时候,会先释放掉autoreleasePool(优先级最低),然后创建一个新的autoreleasePool
  3. 在退出runloop的时候,就释放掉autoreleasePool
3.2.3 那些对象可以加入到autoreleasePool中
  1. 在MRC情况下,使用new,copy,alloc关键字修饰的对象或者retain持有的对象,不能加入到自动释放池中。
  2. MRC中设置为autorelease的对象,不需要手动,自动加入到autoreleasePool中。
  3. 所有autorelease的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。
3.2.4 关于哨兵对象和next指针

next指针只有一个,永远指向下一个能存放autoreleasepool的地址,而哨兵对象有很多个,每个autoreleasepool都对应一个哨兵对象,标示这个autoreleasepool对象从哪里开始存。

3.2.5 next和child:

next指向下一个能存放autoreleasepool对象的地址,child是autoreleasePoolPage的参数,指向下一个page。

保存autoreleasePoolPage的双向链表只有一个么?也就是所有线程的autoreleasePoolPage都保存在一个链表中,还是每个线程保存一个自己的链表?并且链表头也就是链表的入口位置是保存在哪里呢?谁来控制呢?
一个线程有自己单独autoreleasePool链表,也有可能没有链表。链表的hotPage存储在TLS中,因为链表是双向的,通过hotpage就可以找到表头和表尾,不需要再单独存储表头。

需要自己手动添加autoreleasepool的情况

  • 编写的不是基于UI框架的程序,例如命令行工具;
  • 通过循环方式创建大量临时对象;
  • 使用非Cocoa程序创建的子线程;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进程学习

今天开始了进程的学习,主要讲了进程的一些命令以及进程的创建、进程的调度、进程相关的函数接口和进程消亡。在函数接口中要注意子进程和父进程的关系以及回收顺序,避免出现僵尸进程。

C基础练习(学生管理系统)

1.系统运行,打开如下界面。列出系统帮助菜单(即命令菜单),提示输入命令 2.开始时还没有录入成绩,所以输入命令 L 也无法列出成绩。应提示“成绩表为空!请先使用命令 T 录入学生成绩。” 同理,当…

【为什么不要买运营商的机顶盒?解锁智能电视新体验,从一台刷机机顶盒开始】

【置顶:机顶盒刷机步骤请跳转此链接】 在这个数字化飞速发展的时代,电视早已不再是单一的播放工具,它正逐步演变成为家庭娱乐与信息获取的综合中心。然而,许多家庭在选择机顶盒时,往往会因为惯性或便利而直接选择运营商提供的机顶…

基于Tasking编译器AURIX TC3xxDemo工程创建

1、示例代码获取 1.1 英飞凌AURIX系列示例代码路径 Infineon/AURIX_code_examples: This repository contains code example projects for the AURIX™ Development Studio. (github.com) 1.2 代码获取 源代码获取有两种方式,一种通过git拉到本地仓库&#xff0…

修改微信(3.9.10.19版本)系统托盘图标(傻瓜教程)

微信版本: 进行以下操作先退出微信 1.iconfont Logo下载一个图标png,大小为256像素,前面颜色自己看着弄 2.png转ico,转化链接(转化的网站很多不一定非要是这个) 3.下载后续所需程序(ResHacker和IconWo…

【动态规划】力扣918. 环形子数组的最大和

给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 n) % n] 。 …

Jmeter性能压测4000并发

性能测试的底层逻辑 程序为什么会有性能问题 用户操作 客户端(web/app/小程序)触发网络请求,服务器处理大量网络请求代码运行需要大量服务器资源(CPU、内存、网络、磁盘等等) 资源不是无限,硬件配置不是随…

python-分享篇-英文短文自动分词写入文本文件

文章目录 准备代码效果 准备 代码 import string f open(./data/split.txt) sf.read() str1s.title() print(str1) print("".join([s for s in str1.splitlines(True) if s.strip()])) list1 str1.split() # 采用默认分隔符进行分割 #字符串列表去重 l1list(set(l…

lvs的dr模式综合实践

目录 ​编辑虚拟机准备工作 ​编辑​编辑​编辑 配置过程 配置client主机 配置router主机 配置lvs主机(vip使用环回来创建) 配置server1主机(vip使用环回来创建) 配置server2主机(vip使用环回来创建&#xff0…

SpringBoot+Mybatis 分页

无论多数据源,还是单数据源,分页都一样,刚开始出了点错,是因为PageHelper的版本问题 这里用的SpringBoot3 SpringBoot2应该是没有问题的 相关代码 dynamic-datasourceMybatis多数据源使用-CSDN博客 依赖 <?xml version"1.0" encoding"UTF-8"?&g…

Maven+Tomcat环境搭建

Maven Maven框架的作用 1.在JavaWeb开发中需要使用大量的jar包,这些jar包需要手动导入 2.自动导入和配置jar包 Maven项目架构管理工具 方便导入jar包 Maven的核心思想:约定大于配置 有约束不要去违反 Maven会规定好你该如何去编写java代码,必须按照规范来 安装流程 1…

Godot学习笔记8——PONG游戏制作

目录 一、小球 二、地图 三、积分系统 四、玩家场景 五、导出与发布 PONG是1972年由雅达利公司推出的游戏&#xff0c;主要玩法为玩家控制两个可以上下移动的板子击打屏幕中不断运动的球 一、小球 我们首先创建一个“Area2D”场景&#xff0c;在它下方创建“Collisi…

好书推荐|复旦大学专家团队著《大规模语言模型:从理论到实践》(附PDF)

前言 在这个迅猛发展且复杂多变的技术领域&#xff0c;掌握大型模型的理论核心并有效地将其应用于实践&#xff0c;对许多人来说是一项艰巨的任务。为此&#xff0c;《大规模语言模型&#xff1a;从理论到实践》一书应运而生。本书由复旦大学计算机科学技术学院知名教授张奇领…

将增强型乳腺摄影添加到断层合成中用于有乳腺癌个人病史女性的乳腺癌检测:| 文献速递-基于深度学习的乳房、前列腺疾病诊断系统

Title 题目 Addition of Contrast-enhanced Mammography to Tomosynthesis for Breast Cancer Detection in Women with a Personal History of Breast Cancer: 将增强型乳腺摄影添加到断层合成中用于有乳腺癌个人病史女性的乳腺癌检测&#xff1a; Background 背景 Dig…

【黄啊码】什么是SD?SD的使用技巧

目录 SD的简介 SD的优势 1、不需要绘画基础&#xff0c;便能绘制精美的图片&#xff0c;插画 2、StableDiffusion不仅用于静态图像生成&#xff0c;还可应用于动态图像和视频创作&#xff0c;拓宽了AI绘画的应用领域。 3、快速发展&#xff0c;成熟的社区——帮助使用者…

Linux系统 腾讯云服务/宝塔面板安装《最新版本2024》禅道开源版本20.2

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 有两种方式1.自带有服务器安装和2.使用禅道官方的服务器免费使用 第一种&#xff1a;免费的提供5人使用&#xff0c;存储的数据大小也是有限制的范围的 禅道下载 - 禅道项目管理软件 下滑页面就能…

大事件前端篇JavaScript导入导出

前置知识&#xff1a; 呃呃呃......有那么一点点&#xff1f;&#xff1f;但不多。。。。不管&#xff0c;先学&#xff0c;不会的时候再去看响应的知识点吧。 补充一个模块化相关的知识点&#xff1a;JavaScript-导入导出 JS提供的导入导出机制&#xff0c;可以实现按需导…

尚品汇-创建ES索引库(二十七)

目录&#xff1a; &#xff08;1&#xff09;商品检索功能介绍 &#xff08;2&#xff09;根据业务搭建数据结构 &#xff08;3&#xff09;nested 介绍 &#xff08;4&#xff09;搭建service-list服务 &#xff08;5&#xff09;构建实体与es mapping建立映射关系 &…

前端常用的几个工具网站

觉得不错的前端工具类网站 1、Grid布局生成 https://cssgrid-generator.netlify.app 2、拟物按钮样式生成 https://neumorphism.io 3、玻璃形态效果 在线制作CSS玻璃形态 4、一些Button、checkBox、switch、card的css样式 零代码 - 精美CSS样式库 5、CSS阴影生成 在线创建…

python游戏开发之五子棋游戏制作

五子棋是一种源自中国的传统棋类游戏&#xff0c;起源可以追溯到古代。它是一种两人对弈的游戏&#xff0c;使用棋盘和棋子进行。棋盘通常是一个 1515 的网格&#xff0c;棋子分为黑白两色&#xff0c;双方轮流在棋盘上落子。游戏的目标是通过在棋盘上落子&#xff0c;使自己的…