面试好文

iOS原理 AutoreleasePool的结构分析

2021-01-13  本文已影响0人  东篱采桑人

iOS原理 文章汇总

上一篇文章介绍了AutoreleasePool的基本概念,本文将分析AutoreleasePool的内存结构。

分析入口

在OC中,使用@autoreleasepool {}代码块可以手动创建一个AutoreleasePool,若想知道这个代码块内部做了哪些处理,可以通过Clang编译汇编调试这两个方式来分析。

int main(int argc, char * argv[]) {
    
    @autoreleasepool {
        
    }
    
    return 0;
}

在终端通过Clang命令行将main.m文件编译成main.cpp文件,可以看到底层编译如下:

struct __AtAutoreleasePool {
    //构造函数
    __AtAutoreleasePool() {
            atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    //析构函数
    ~__AtAutoreleasePool() {
            objc_autoreleasePoolPop(atautoreleasepoolobj);
     }
      void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {

   //这里的{}表示autoreleasepool的作用域
   { 
         __AtAutoreleasePool __autoreleasepool; 
    }

    return 0;
}

//Clang命令行
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m

在main函数中下断点,再设置Xcode的Debug -> Debug Workflow -> Always Show Disassembly,就可以查看汇编调用流程,如下图所示:

从两种方式的分析结果可知,@autoreleasepool {}代码块实现逻辑如下:

因此,AutoreleasePool的底层原理,可以通过在objc源码中分析这两个函数的实现逻辑来得知。

源码中对AutoreleasePool的描述

在源码中,对AutoreleasePool作了简单的描述,有助于后面分析其结构。

Autorelease pool implementation

- A thread's autorelease pool is a stack of pointers.
线程的自动释放池是指针的堆栈。

- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每一个指针代表一个需要 release 的对象或者 POOL_SENTINEL(哨兵对象,代表一个 autoreleasepool 的边界);

- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
一个 pool token 就是这个 pool 所对应的 POOL_SENTINEL 的内存地址。当这个 pool 被 pop 的时候,所有内存地址在 pool token 之后的对象都会被释放。

- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
这个堆栈被划分成了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增加或删除;

- Thread-local storage points to the hot page, where newly autoreleased objects are stored.
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。

通过描述,可知:

分析自动释放池每页的结构

在源码中,objc_autoreleasePoolPushobjc_autoreleasePoolPop的实现如下:

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

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

可以看到,两个函数实际上是调用AutoreleasePoolPagepushpop方法,这个AutoreleasePoolPage就是自动释放池列表里的页。

AutoreleasePoolPage
//************宏定义************
#define PROTECT_AUTORELEASEPOOL 0
#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)   //4096
#define PAGE_MIN_MASK           (PAGE_MIN_SIZE-1)       //4095

//************类定义************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    //页的大小,为PAGE_MIN_SIZE
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif

private:
    
    ...
    static size_t const COUNT = SIZE / sizeof(id);
    
    //分配内存 
    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    //释放
    static void operator delete(void * p) {
        return free(p);
    }

    //构造函数
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),//开始存储的位置
                                objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
                                newParent ? newParent->hiwat : 0)
    {...}
    
    //析构函数
    ~AutoreleasePoolPage() {...}

    //杀掉
    void kill() {...}

    //other private funcs  
    ...  ...

public:

    //自动释放
    static inline id autorelease(id obj){...}

    //入栈
    static inline void *push() {...}

    //出栈
    static inline void
    pop(void *token){...}
   
    //other public funcs
    ... ...    
}

AutoreleasePoolPage的结构中可以看出:

AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
    //用来校验AutoreleasePoolPage的结构是否完整
    magic_t const magic;   //16字节
    //下次新添加的autoreleased对象的位置,初始化时指向begin()
    __unsafe_unretained id *next;   //8字节
    //当前线程
    pthread_t const thread;   //8字节
    //指向父节点,即上一个页面,第一个页面的parent值为nil
    AutoreleasePoolPage * const parent;   //8字节
    //指向子节点,即下一个页面,最后一个页面的child值为nil
    AutoreleasePoolPage *child;   //8字节
    //表示页面深度,从0开始,往后递增1
    uint32_t const depth;   //4字节
    //high water mark,表示最大入栈数量标记
    uint32_t hiwat;   //4字节

    //初始化
    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

//总共16字节
struct magic_t {
       
    //静态变量不占用结构体的内存
    static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;

    //数组里有4个uint32_t类型的成员,总共16字节
    uint32_t m[4]; 
      
    //其他函数
    ... ...
};

AutoreleasePoolPageData结构中可知:

由于自动释放池里的每一页都是一个AutoreleasePoolPage对象,AutoreleasePoolPage又继承自AutoreleasePoolPageData,因此可以得知,自动释放池每一页的结构如下:

查看自动释放池的内存情况

上面分析了page的结构,接下来通过具体Demo来打印自动释放池的内存。

//打印AutoreleasePool
extern void _objc_autoreleasePoolPrint(void);

int main(int argc, char * argv[]) {
    
    @autoreleasepool {
        
        //往自动释放池里添加count数量的对象
        NSInteger count = 对象数量;    
        for(NSInteger i=0; i<count; i++){
            
            NSObject *obj = [[NSObject alloc] autorelease];
        }
        
        //调用
        _objc_autoreleasePoolPrint();
    }

    return 0;
}
objc[85067]: ##############
objc[85067]: AUTORELEASE POOLS for thread 0x1113a2dc0
objc[85067]: 4 releases pending.
//page地址,第一页的地址即为自动释放池的地址
objc[85067]: [0x7fbfa380b000]  ................  PAGE  (hot) (cold)
//哨兵
objc[85067]: [0x7fbfa380b038]  ################  POOL 0x7fbfa380b038     
//添加的3个对象,存放的是对象的地址指针
objc[85067]: [0x7fbfa380b040]    0x600003e2c020  NSObject
objc[85067]: [0x7fbfa380b048]    0x600003e2c030  NSObject
objc[85067]: [0x7fbfa380b050]    0x600003e2c040  NSObject
objc[85067]: ##############

从打印结果可知,当往自动释放池里添加3个对象时:

每页page的大小为4096字节,减去自身成员占用的56字节,能存储的指针数量为:(4096 - 56) / 8 = 505个,这些指针指向的是哨兵和自动释放对象的地址。

objc[92734]: ##############
objc[92734]: AUTORELEASE POOLS for thread 0x117e48dc0
objc[92734]: 1011 releases pending.
//第1页,page成员 + 1个哨兵 + 504个对象
objc[92734]: [0x7fee2d00f000]  ................  PAGE (full)  (cold)
objc[92734]: [0x7fee2d00f038]  ################  POOL 0x7fee2d00f038
objc[92734]: [0x7fee2d00f040]    0x600001198030  NSObject
objc[92734]: [0x7fee2d00f048]    0x600001198050  NSObject
objc[92734]: [0x7fee2d00f050]    0x600001198060  NSObject
...  ...
objc[92734]: [0x7fee2d00ffe8]    0x600001199f90  NSObject
objc[92734]: [0x7fee2d00fff0]    0x600001199fa0  NSObject
objc[92734]: [0x7fee2d00fff8]    0x600001199fb0  NSObject
//第2页,page成员 + 0个哨兵 + 505个对象
objc[92734]: [0x7fee2d011000]  ................  PAGE (full)     
objc[92734]: [0x7fee2d011038]    0x600001199fc0  NSObject
objc[92734]: [0x7fee2d011040]    0x600001199fd0  NSObject
objc[92734]: [0x7fee2d011048]    0x600001199fe0  NSObject
... ...
objc[92734]: [0x7fee2d011fe8]    0x60000119bf20  NSObject
objc[92734]: [0x7fee2d011ff0]    0x60000119bf30  NSObject
objc[92734]: [0x7fee2d011ff8]    0x60000119bf40  NSObject
//第3页,page成员 + 0个哨兵 + 1个对象
objc[92734]: [0x7fee2d00b000]  ................  PAGE  (hot) 
objc[92734]: [0x7fee2d00b038]    0x60000119bf50  NSObject
objc[92734]: ##############

从这个打印结果可知,当往自动释放池里添加505 * 2个对象时,当前总共创建了三页page

因此,一个自动释放池只有一个哨兵,存放在第一页。每页最多存放505个指针,第一页满状态存放的是1个哨兵和504个对象地址,其他页存放的是505个对象地址。存满的页被标记为full page,正在操作的页被标记为hot page

综上所述

AutoreleasePool的结构

经过分析,AutoreleasePool的内存结构如上图所示,特点如下:

上一篇 下一篇

猜你喜欢

热点阅读