KAOLA's note.

自动释放池的原理

字数统计: 1.6k阅读时长: 7 min
2018/11/26 Share

背景

前文提到了weak修饰对象原理。对于iOS如何管理内存的释放销毁,还需要知道自动释放池技术,故本文会在源码的基础上讨论。

autoreleasepool总结:

  1. ARC下,非alloc/new/copy/mutableCopy开头的类方法默认返回的是autoreleased对象,编译器会自动插入相关的autorelease语句
  2. 主线程的runloop进入时(entry),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
  3. 准备进入休眠(BeforeWaiting) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;即将退出Loop(Exit) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。

问题

  1. 池内对象何时push、pop,
  2. 池不够用了怎么办
  3. POOL_BOUNDARY的用途

官方文档

笔记

构造函数、析构函数

1
2
3
4
5
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

自动释放池的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
/***********************************************************************
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.
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.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/

1. 解释

  • 线程的自动释放池是一个指针栈
  • 每个指针代表了要释放的对象或者是POOL_BOUNDARY(自动释放池的边界对象)
  • 自动释放池pop时,在POOL_BOUNDARY之后push进来的对象会被释放
  • 这个栈由若干双向列表构成。每页会在在必要的情况下增、删
  • TLS(Thread-local storage)指向最近储存的page(hotpage)

TLS: Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread.

2. 定义or概念

  • AutoreleasePool是以AutoreleasePoolPage为单位构造的双向链表
  • 对象发出autorelease消息后 会加入AutoreleasePoolPage中。
  • 调用autorelease::pop时,会向AutoreleasePoolPage中的对象发送release消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class AutoreleasePoolPage 
{
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)

# define POOL_BOUNDARY nil //分界线
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);

magic_t const magic;// magic_t
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;

....

id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
........
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
.......

void releaseAll()
{
releaseUntil(begin());
}


}


3. 消息调用栈

  • 对象发出autorelease

    • rootAutorelease
      • rootAutorelease2
        • AutoreleasePoolPage::autorelease
          • autoreleaseFast
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            static inline id *autoreleaseFast(id obj)
            {
            AutoreleasePoolPage *page = hotPage();//取出hotPage
            if (page && !page->full()) {//page存在且没满
            return page->add(obj);//对象加入该page
            } else if (page) {//该页已满
            return autoreleaseFullPage(obj, page);//调用autoreleaseFullPage 替换当前的hotPAge 然后再执行add(obj)
            } else {//当前没有page
            return autoreleaseNoPage(obj);//调用autoreleaseNoPage 新增page 然后再执行add(obj)
            }
            }

4. 实现细节

hotPage()

1
2
3
4
5
6
7
8
static inline AutoreleasePoolPage *hotPage() 
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);//根据tls取出最近储存的page
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}

add(obj)

1
2
3
4
5
6
7
8
9
10

id *add(id obj)
{
assert(!full());
unprotect();//
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//并没有判断obj==nil
protect();
return ret;
}

autoreleaseFullPage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.
//该页已满,向链表的child方向遍历,直到找到不满的page为止,如果遍历完还是没有符合要求的page,则new一个,并标记为hotPage,
//在hotpage中加入obj

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);
}

autoreleaseNoPage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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 yet
assert(!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",
pthread_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();
}

// We are pushing an object or a non-placeholder'd pool.

// Install the first page.
//new一个并标记为hotpage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);

// Push a boundary on behalf of the previously-placeholder'd pool.
//当 pushExtraBoundary 加入一个nil对象 作为边界标记
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}

// Push the requested object or pool.
//obj加入
return page->add(obj);
}
  • objc_autoreleasePoolPop
    • AutoreleasePoolPage::pop
      • releaseUntil

releaseUntil:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    void releaseUntil(id *stop) 
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage

while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();

// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}

page->unprotect();
//从next开始往前取
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
//直到POOL_BOUNDARY为止,向obj发送release消息
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
//重新标记hotPage
setHotPage(this);

#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}

回顾总结和问题

  1. ARC下,非alloc/new/copy/mutableCopy开头的类方法默认返回的是autoreleased对象,编译器会自动插入相关的autorelease语句
  2. 主线程的runloop进入时(entry),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
  3. 准备进入休眠(BeforeWaiting) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;即将退出Loop(Exit) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。

  4. 池内对象何时push、pop,

  5. 池不够用了怎么办
  6. POOL_BOUNDARY的用途

总结补充:

  • 当对象发送autorelease消息时,有三种情况
    • hotPage&&!full
      • page->add(obj)
    • hotPage&&full
      • autoreleaseFullPage
      • page->add(obj)
    • !hotPage
      • autoreleaseNoPage
      • page->add(obj)

总结补充

  1. 当调用AutoreleasePoolPage::pop时,会向栈内的对象发送release消息
  2. 刚刚提到我们会在new一个page的时候压入一个nil(POOL_BOUNDARY)作为边界标记,1中提到的发送release就是到这个边界位置

项目中的应用

CATALOG
  1. 1. 背景
  2. 2. autoreleasepool总结:
  3. 3. 问题
  4. 4. 笔记
    1. 4.1. 构造函数、析构函数
    2. 4.2. 自动释放池的实现原理
      1. 4.2.1. 1. 解释
      2. 4.2.2. 2. 定义or概念
      3. 4.2.3. 3. 消息调用栈
      4. 4.2.4. 4. 实现细节
        1. 4.2.4.1. hotPage()
        2. 4.2.4.2. add(obj)
        3. 4.2.4.3. autoreleaseFullPage
        4. 4.2.4.4. autoreleaseNoPage
  5. 5. 回顾总结和问题
  6. 6. 总结补充:
  7. 7. 总结补充
  8. 8. 项目中的应用