自动释放池

iOS应用的主线程在每次runloop开始的时候创建自动释放池,在runloop结束的时候释放自动释放池。如果在一个runloop内,应用程序创建了大量临时对象,自动释放池可以减少内存峰值。

for (int i = 0; i < 1000000; i++) {NSString *string = [NSString stringWithFormat:@"hello world!"];
}

如果执行上面的代码,使用XCode可以看到有明显的短暂内存上涨。

for (int i = 0; i < 1000000; i++) {@autoreleasepool {NSString *string = [NSString stringWithFormat:@"hello world!"];}
}

增加自动释放池以后,程序的内存占用没有明显的上涨,这个时候自动释放池释放了临时创建的变量内存。

自动释放池push和pop时机

autoreleasePool的push/pop和runloop有关。

在runloop进入kCFRunLoopEntry状态时,调用objc_autoreleasePoolPush方法

runloop进入kCFRunLoopBeforeWaiting状态时,调用objc_autoreleasePoolPop方法和objc_autoreleasePoolPush方法

runloop进入kCFRunLoopBeforeExit状态时,调用objc_autoreleasePoolPop方法。

自动释放池数据结构

struct AutoreleasePoolPageData
{#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSstruct AutoreleasePoolEntry {uintptr_t ptr: 48;uintptr_t count: 16;static const uintptr_t maxCount = 65535; // 2^16 - 1};static_assert((AutoreleasePoolEntry){ .ptr = OBJC_VM_MAX_ADDRESS }.ptr == OBJC_VM_MAX_ADDRESS, "OBJC_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endifmagic_t const magic;   //校验page是否完整__unsafe_unretained id *next;   //指向下一个存放数据的地址objc_thread_t const thread;     //当前线程AutoreleasePoolPage * const parent;  //父结点AutoreleasePoolPage *child;       //子结点uint32_t const depth;   //page深度uint32_t hiwat;   //最大入栈数量AutoreleasePoolPageData(__unsafe_unretained id* _next, objc_thread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat): magic(), next(_next), thread(_thread),parent(_parent), child(nil),depth(_depth), hiwat(_hiwat){}
};

AutoreleasePoolPageData是AutoreleasePoolPage的基类。

自动释放池是双向链表的数据结构,parent指针指向父结点,child指向子结点,每页的数据大小是4096字节大小,每个页面是双向链表的一个结点。next代表的是当前页面可插入数据的位置。

自动释放池实现原理

push过程

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

当代码中向自动释放池加入对象的时候,Objective-C内部会调用这个方法。

 static inline void *push() {ReturnAutoreleaseInfo info = getReturnAutoreleaseInfo();moveTLSAutoreleaseToPool(info);id *dest;if (slowpath(DebugPoolAllocation)) {// Each autorelease pool starts on a new pool page.dest = autoreleaseNewPage(POOL_BOUNDARY);} else {dest = autoreleaseFast(POOL_BOUNDARY);}ASSERT(dest == (id *)EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;}

这个方法主要是调用了autoreleaseFast方法。

 static inline id *autoreleaseFast(id obj){AutoreleasePoolPage *page = hotPage();if (page && !page->full()) {return page->add(obj);} else if (page) {return autoreleaseFullPage(obj, page);} else {return autoreleaseNoPage(obj);}}

autoreleaseFast方法用于获取当前的hotPage,如果hotPage没有满直接插入数据,如果已经满了,重新分配一个page的空间然后插入,如果没有获取到page说明当前的自动释放池还没有初始化,需要初始化自动释放池。

id *add(id obj){ASSERT(!full());unprotect();id *ret;ret = next;  // faster than `return next-1` because of aliasing*next++ = obj;done:protect();return ret;}

add方法精简后,可以看到add方法实际是把obj放入到next指针的位置,并把next++,函数的返回值是obj的存放地址。

static __attribute__((noinline))id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page){// The hot page is full. // Step to the next non-full page, adding a new page if necessary.// Then add the object to that page.ASSERT(page == hotPage());ASSERT(page->full()  ||  DebugPoolAllocation);do {if (page->child) page = page->child;else page = new AutoreleasePoolPage(page);} while (page->full());setHotPage(page);return page->add(obj);}

autoreleaseFullPage函数是找到一个没有满的页面,调用add函数插入obj对象

static __attribute__((noinline))id *autoreleaseNoPage(id obj){// "No page" could mean no pool has been pushed// or an empty placeholder pool has been pushed and has no contents yetASSERT(!hotPage());bool pushExtraBoundary = false;if (haveEmptyPoolPlaceholder()) {// We are pushing a second pool over the empty placeholder pool// or pushing the first object into the empty placeholder pool.// Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder.pushExtraBoundary = true;}else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {// We are pushing an object with no pool in place, // and no-pool debugging was requested by environment._objc_inform("MISSING POOLS: (%p) Object %p of class %s ""autoreleased with no pool in place - ""just leaking - break on ""objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj));objc_autoreleaseNoPool(obj);return nil;}else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {// We are pushing a pool with no pool in place,// and alloc-per-pool debugging was not requested.// Install and return the empty pool placeholder.return setEmptyPoolPlaceholder();}// 初始化首页AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);setHotPage(page);// 增加boundry标识if (pushExtraBoundary) {page->add(POOL_BOUNDARY);}// 添加objreturn page->add(obj);}

如果当前没有自动释放池页面的时候,需要初始化AutoreleasePoolPage,然后调用add方法添加obj。这里setHotPage的作用是标识当前页面。

pop过程

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

自动释放池在pop时会调用这个方法。

    static inline voidpop(void *token){// We may have an object in the ReturnAutorelease TLS when the pool is// otherwise empty. Release that first before checking for an empty pool// so we don't return prematurely. Loop in case the release placed a new// object in the TLS.while (releaseReturnAutoreleaseTLS());AutoreleasePoolPage *page;id *stop;if (token == (void*)EMPTY_POOL_PLACEHOLDER) {// 获取hotPagepage = hotPage();if (!page) {// 自动释放池没有使用过,清空placeholderreturn setHotPage(nil);}// Pool was used. Pop its contents normally.// Pool pages remain allocated for re-use as usual.page = coldPage();token = page->begin();} else {page = pageForPointer(token);}stop = (id *)token;if (*stop != POOL_BOUNDARY) {if (stop == page->begin()  &&  !page->parent) {// Start of coldest page may correctly not be POOL_BOUNDARY:// 1. top-level pool is popped, leaving the cold page in place// 2. an object is autoreleased with no pool} else {return badPop(token);}}if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {return popPageDebug(token, page, stop);}//清理自动释放池return popPage<false>(token, page, stop);}

在pop时会判断是否为EMPTY_POOL_PLACEHOLDER,POOL_BOUNDARY等标记。这个函数最后调用的popPage函数。

template<bool allowDebug>static voidpopPage(void *token, AutoreleasePoolPage *page, id *stop){if (allowDebug && PrintPoolHiwat) printHiwat();//释放当前页面page->releaseUntil(stop);// memory: delete empty childrenif (allowDebug && DebugPoolAllocation  &&  page->empty()) {//debug使用// special case: delete everything during page-per-pool debuggingAutoreleasePoolPage *parent = page->parent;page->kill();setHotPage(parent);} else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {// special case: delete everything for pop(top)// when debugging missing autorelease poolspage->kill();setHotPage(nil);} else if (page->child) {// 如果页面实际占用空间小于一半,将child结点销毁if (page->lessThanHalfFull()) {page->child->kill();} 如果页面实际占用空间大于一半,将child结点保留else if (page->child->child) {page->child->child->kill();}}}
    void releaseUntil(id *stop) {// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbagedo {while (this->next != stop) {AutoreleasePoolPage *page = hotPage();while (page->empty()) {page = page->parent;setHotPage(page);}page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSAutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;// create an obj with the zeroed out top byte and release thatid obj = (id)entry->ptr;int count = (int)entry->count;  // grab these before memset
#elseid obj = *--page->next;
#endifmemset((void*)page->next, SCRIBBLE, sizeof(*page->next));page->protect();if (obj != POOL_BOUNDARY) {#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS// release count+1 times since it is count of the additional// autoreleases beyond the first onefor (int i = 0; i < count + 1; i++) {objc_release(obj);}
#elseobjc_release(obj);
#endif}}// Stale return autorelease info is conceptually autoreleased. If// there is any, release the object in the info. If stale info is// present, we have to loop in case it autoreleased more objects// when it was released.} while (releaseReturnAutoreleaseTLS());setHotPage(this);#if DEBUG// 检查子结点是否清空for (AutoreleasePoolPage *page = child; page; page = page->child) {ASSERT(page->empty());}
#endif}

releaseUntil函数会清空一个自动释放池,在释放对象的时候实际调用的是objc_release函数。

iOS中内存自动释放池相关推荐

  1. ios中的自动释放池

    自动释放池中是否有虑重功能 1 @autoreleasepool { 2 UIView *view = [UIView alloc] init] autorelease]; 3 [view autor ...

  2. 第六讲:Obj-C 内存管理4 - 自动释放池

    转:http://tigercat1977.blog.163.com/blog/static/2141561122012111294616203/ 第六讲:Obj-C 内存管理4 - 自动释放池 主要 ...

  3. (转)Objective-C Autorelease Pools(自动释放池)详解

    本篇将给您介绍"Autorelease Pools"(自动释放池)在应用中的使用技巧. 1,Autorelease Pools概要 一个"Autorelease Pool ...

  4. 【深入Cocos2d-x】探索Cocos2d-x中的内存管理-引用计数和自动释放池

    2019独角兽企业重金招聘Python工程师标准>>> #深入Cocos2d-x-探索Cocos2d-x中的内存管理-引用计数和自动释放池 ###引用计数(Reference Cou ...

  5. iOS内存管理 —— 自动释放池和runloop

    iOS内存管理 -- 自动释放池和runloop 1. 自动释放池 1.1 自动释放池介绍 1.2 自动释放池底层原理 objc_autoreleasePoolPush autoreleaseNoPa ...

  6. 【iOS高级资深工程师面试篇】④、2022年,金九银十我为你准备了《iOS高级资深工程师面试知识总结》 内存管理部分2/2 引用计数-弱引用-自动释放池-循环引用

    iOS高级资深工程师面试篇系列 - 已更新3篇 UI部分1/3 -UITableView-事件传递&视图响应 UI部分2/3 -图像显示原理-UI卡顿&掉帧 UI部分3/3 -UIVi ...

  7. 内存管理-定时器循环、内存布局、tagged pointer、weak指针、copy、自动释放池

    先上代码,我们平时用的定时器,cadisplaylink.nstimer,CADisplayLink.NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用 ...

  8. iOS 多线程 自动释放池常见面试题代码

    同步:代码依次执行,一个人执行多个任务,也是依次执行.一个人在同一时间只执行一个任务 异步:可以同时执行多个任务 进程:指在系统中运行的应用程序,每个进程都是独立的,都有独立的且安全的运行空间 线程: ...

  9. iOS之深入解析自动释放池autoreleasepool的底层原理

    一.自动释放池 autoreleasepool 原理 自动释放池是 OC 中的一种内存自动回收机制,它可以将加入 autoreleasePool 中的变量 release 的时机延迟. 简单来说,就是 ...

最新文章

  1. 宏基因组分析技术研讨会-2019年最后一期
  2. 安装Python2.7出现configure: error: no acceptable C compiler found in $PATH错误
  3. 用python画烟花-用python实现漂亮的烟花demo
  4. ubuntu20配置阿里源简单粗暴的方法
  5. Kubernetes Federation V2搭建(持续更新)
  6. PlateSpin 完全复制由于LVM没有可用空闲空间导致失败
  7. 概念验证_设置成功的UX概念验证
  8. 74cms骑士人才招聘网系统网站源码 SE版
  9. react和nodejs_如何使用React,TypeScript,NodeJS和MongoDB构建Todo应用
  10. pycharm新建项目怎么选择框架_必看!心血管疾病怎么选择检查项目?
  11. Python3 encode中的unicode-escape和raw_unicode_escape
  12. Oracle下sqlplus无法使用命令退格删除和历史记录的解决方法--使用rlwrap
  13. VS2010 配置PCL1.6.0AII in one 无法启动程序ALL_BUILD
  14. bt种子文件是什么(包括bt文件结构)
  15. 第十二章 Android第三方库源码
  16. 广州三本找Java实习经历
  17. sm专用计算机是啥意思,计算机CPU的主频代表的是什么意思
  18. ISBN(国际标准书号)的校验
  19. 通俗地讲解目标检测中AP指标
  20. java抓取网站数据

热门文章

  1. 计算机软考中级科目哪个最容易过?
  2. 华为鸿蒙重新编译,华为再次官宣鸿蒙系统发布时间,系统将自带不可卸载的应用...
  3. WPS的查找函数VLOOKUP的使用
  4. fiddle抓取html代码,不懂html也来学网抓(xmlhttp/winhttp+fiddler)
  5. 区块链+公证掀起落地潮,上海再添一例
  6. 计算机二级题库一邮件合并无法打开数据源,WPS邮件合并打不来数据表(wps文字使用邮件合并时打不开数据源)...
  7. 音视频之解析flv文件实战
  8. 通过邮箱找到steam账户_如何在Steam上实际找到好的游戏
  9. web安全渗透测试基础知识
  10. 全网最生动易懂,495页Python漫画教程,高清PDF版限时分享