浅析AutoreleasePool源码

简介: 最近在拜读Draveness大佬的一篇文章自动释放池的前世今生 ---- 深入解析 autoreleasepool,看到文中给读者留了一个问题:我到现在也不是很清楚为什么要根据当前页的不同状态 kill 掉不同 child 的页面。

最近在拜读Draveness大佬的一篇文章自动释放池的前世今生 ---- 深入解析 autoreleasepool,看到文中给读者留了一个问题:

我到现在也不是很清楚为什么要根据当前页的不同状态 kill 掉不同 child 的页面。

关于AutoreleasePool是什么,强力推荐阅读原文,写的很好。这里就不说了,直接讨论问题。

首先是整个pop方法的实现:

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        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 {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half fully
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

我们先看看释放的函数releaseUntil,它在释放的时候其实会一直顺着parent往前释放,直到参数stop,也就是说可能一次性释放好几个page

// 代码有所删减
void releaseUntil(id *stop)
{
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();
        
        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }
        
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        
        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }
    
    setHotPage(this);
}

然后我们来看看这段有疑问的代码

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {  // 分支1
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) { // 分支2
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) { // 分支3
            // hysteresis: keep one empty child if page is more than half fully
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }

这块代码的作用是删除空的子节点,释放内存。pop之后三种情况:

  1. 当前page为空,直接kill掉当前page,然后把parent设置为hotpage
  2. 当前page为空,而且没有parent,kill掉当前pagehotpage置为空;
  3. 当前page不为空,但是有child,如果当前page的空间占用不到一半,释放child,如果当前page的空间占用超过一半,且child还有child,直接释放这个孙子辈的page。(对于第三步注释中的解释是:keep one empty child if page is more than half fully)

我们再看看kill的实现,可以发现他是会顺着child一直往后释放,保证释放节点的child page都被释放了。

void kill()
{
    AutoreleasePoolPage *page = this;
    while (page->child) page = page->child;
    
    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        page = page->parent;
        if (page) {
            page->unprotect();
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

到这里就可以得出结论了:

  1. pop之后,所有child page肯定都为空了,且当前page一定是hotPage
  2. 系统为了节约内存,判断,如果当前page空间使用少于一半,就释放掉所有的child page,如果当前page空间使用大于一半,就从孙子page开始释放,预留一个child page
目录
相关文章
|
7月前
|
算法 NoSQL 安全
30万的源码和300的源码有什么区别?
价格差异巨大的源码(30万对300)主要区别在于质量、完整性和技术支持。高质量源码通常有清晰结构、高效算法、良好文档,提供全面的技术支持,安全稳定且来自信誉良好的开发者。而低价源码可能存在问题、缺乏文档和支持。选择时需结合实际需求,注意测试和评估。示例中的AI导诊系统和云HIS系统源码,提供完整文档、数据库和二次开发支持,适用于不同场景,能有效提升开发效率和项目质量。
30万的源码和300的源码有什么区别?
|
7月前
看源码的方法
看源码的方法
96 1
|
JavaScript
源码
源码
|
程序员 开发工具 C++
|
消息中间件 网络协议 Java
eventMesh源码学习
eventMesh源码学习
209 0
|
安全 Java
ReentranLock源码学习
线程的三大特性:原子性、可见性、有序性。也就是说满足这个三个特性的操作都是可以保证安全的,如Atomic包、volatile、通过happensBefore原则可以进行线程的安全的判断,这个依据通常是为了避免jvm指令重排。比如通常我们知道的配置信息,如果有多个线程去进行配置信息的修改,则需要进行上锁。或者多个线程修改一个变量时,此时就需要进行上锁了,或者读写分离时,可以考虑ReentrantReadWriteLock等。其本质是解决并行中的问题,将并行转成串行问题进行解决。那怎么上锁才有用呢?锁的状态大部分情况下是互斥的。当然也有特例:ReentrantReadWriteLock的读读是不会
96 0
ReentranLock源码学习
|
存储 人工智能 安全
C++学习必备——文章中含有源码
C++学习必备——文章中含有源码
122 0
C++学习必备——文章中含有源码
openFrameworks下的肤色检测源码
openFrameworks下的肤色检测源码
170 0
|
存储 Android开发 C++
看Ogrekit源码的小结
看Ogrekit源码的小结
113 0
|
算法 NoSQL 前端开发
为什么要看源码、如何看源码,高手进阶必看
为什么要看源码、如何看源码,高手进阶必看
276 0