自动释放池(autoreleasepool)
AutoreleasePool简介
- Apple官方说明:一个用来支持引用计数内存管理系统的对象;
从Apple官方文档中我们可以得出以下比较重要的几点
- 如果你使用ARC,你不能直接
NSAutoreleasePool
,你可以使用@autoreleasepool
block. NSAutoreleasePool
对象不能调用retain
和autorelease
;1
2
3
4
5autorelease
Raises an exception. // 抛出异常
retain
Raises an exception. // 抛出异常- 在引用计数环境中,一个
NSAutoreleasePool
对象内部包含了很多接受到autorelease
消息的对象,当NSAutoreleasePool
对象释放后,它将向其包含的每个对象发送release
消息,因此给对象发送autorelease
消息比release
消息,延长了该对象的寿命(发送release
可能直接销毁,而调用autorelease
消息,会将其添加到NSAutoreleasePool
中,随着NSAutoreleasePool
的销毁才可能销毁); - 一个对象可以多次放到同一个自动释放池中,这种情况下,该对象将会收到多次的
release
消息; Application Kit
在事件循环开始的时候在主线程创建了一个自动释放池,并且在结束的时候去清空它,从而释放所有进程事件中生成的自动释放的对象。如果使用了Application Kit
,就没必要再去创建自己的自动释放池。
如果你的应用在事件循环中创建了很多临时的
autorelease
的对象,你可以创建一个自动释放池以最大程度地减少峰值内存占用。- 其实这句话的意思大概是这样的:主线程中默认创建了一个自动释放池,由于调用了
autorelease
的对象会延迟释放(等到自动释放池销毁),如果你在应用的事件循环中创建了很多临时的autorelease
对象,你可以自己创建一个自动释放池以最大程度地减少峰值内存占用。
1
2
3
4
5
6
7
8
9
10NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}- 其实这句话的意思大概是这样的:主线程中默认创建了一个自动释放池,由于调用了
疑问1:为什么ARC下还需要使用@autoreleasepool?
- ARC模式只是在编译阶段帮你插入必要的
retain/release/autorelease
等相关代码调用,跟MRC模式下手动调用本质上是没有区别的; - 而NSAutoReleasePool就是用来支持引用计数(Reference Counting)的。
疑问2:在事件循环开始的时候在主线程创建的自动释放池是指
main
方法中的那个@autoreleasepool
?
- 应该不是的,这个事件循环中创建的自动释放池应该是在Runloop中创建的一个自动释放池,和
main
中的自动释放池不是同一个; - 验证:可以将
main
中的自动释放池@autoreleasepool
去掉,能发现对象任然能正常释放;
疑问3:为什么当事件循环中创建了很多临时的
autorelease
对象,你可以自己创建一个自动释放池以最大程度地减少峰值内存占用?
NSAutoreleasePool 底层原理
@autoreleasepool{ }的底层结构
1 | int main(int argc, const char * argv[]) { |
通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
命令我们将OC代码转化为C++源码main.cpp
1 | int main(int argc, const char * argv[]) { |
根据上面编译后的代码我们可以得知:
- 所谓的
@autoreleasePool
block,其实对应着__AtAutoreleasePool
的结构体;
__AtAutoreleasePool 底层结构
1 | extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void); |
@autoreleasepool{ } 的真实面目
1 | int main(int argc, const char * argv[]) { |
在objc-runtime
源码NSObject.mm
中继续探索
objc_autoreleasePoolPush()
objc_autoreleasePoolPop()
1 | void * |
AutoreleasePoolPage
- 关键源码如下:
1 |
|
- 有源码可知
AutoreleasePoolPage
其实是一个C++的类,本质上是一个双向链表
; - 每个
AutoreleasePoolPage
对象占用4096字节
内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
AutoreleasePoolPage::push()
1 | static inline void *push() |
autoreleaseNewPage(id obj)
1 | static __attribute__((noinline)) |
autoreleaseFast(id obj)
1 | static inline id *autoreleaseFast(id obj) |
autoreleaseFullPage(obj, page)
- 当前hotPage已满时调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static __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); // 给传入page新建一个child page
} while (page->full());
setHotPage(page);
return page->add(obj);
} - 查找可用的page:
- 如果传入的 page (已为full状态) 有 child page, 并且这个child page 没有 full,就把这个child page 设置为 hotPage, 并将obj添加这个child page中,(如果child page full 就继续递归查询)
- 如果传入的 page (已为full状态) 并且也没有 child page,那就新建一个page(作为传入的page的child page),并把这个新建的page设置为hotPage;
- 将对象obj添加到hotPage中;
autoreleaseNoPage(id obj)
当前hotpage不存在时调用
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
47static __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 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.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}新建一个全新的page(可以简单的理解为根page),并不是作为了某个page的child page;
设置这个page为hotPage;
可能还会将
POOL_BOUNDARY
先添加到page中;添加obj到page中;
AutoreleasePoolPage::pop(ctxt);
1 | static inline void pop(void *token) |
- 根据传入的地址值token,找到page;
- 向栈中的对象发送release消息,直到遇到第一个边界对象(POOL_BOUNDARY);这句话其实引出了一个问题:到目前为止其实我们只明白了
@autoreleasepool
的底层实现,那么@autoreleasepool
内部的对象是怎么添加到pool中呢?
问题:
@autoreleasepool
中的对象是怎么和pool进行关联的呢?
- 这里就要说一下
autorelease
方法的实现了,毕竟autoreleasepool
是与autorelease
紧密相连的;
1 | - [NSObject autorelease] |
有调用栈可知
@autoreleasepool
内部的对象在调用autorelease
方法时,最终调用了AutoreleasePoolPage
的autoreleaseFast(obj)
,将其添加到了page中;也就是说:
- 创建这个pool的时候调用了
AutoreleasePoolPage::autoreleaseFast(obj)
,此时传入的obj是边界对象(POOL_BOUNDARY
); - pool内部的对象调用
autorelease
,最终还是调用了AutoreleasePoolPage::autoreleaseFast(obj)
,只不过这时的obj就是这个具体的对象了;
- 创建这个pool的时候调用了