介绍
自动释放池是Objective-C/Swift中的一种内存自动回收机制,AutoreleasePool可以将其中的变量进行release的时机延迟。简单来说,就是当创建一个对象,在正常情况下,变量会在超出其作用域的时立即release。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到runloop休眠/超出@autoreleasepool作用域{}之后才会被释放。
官方文档
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.
AppKit 和 UIKit 框架在事件循环(RunLoop)的每次循环开始时,在主线程创建一个自动释放池,并在每次循环结束时销毁它,在销毁时释放自动释放池中的所有autorelease对象。
通常情况下我们不需要手动创建自动释放池,但是如果我们在循环中创建了很多临时的autorelease对象,则手动创建自动释放池来管理这些对象可以很大程度地减少内存峰值。
创建一个自动释放池
在MRC下,可以使用NSAutoreleasePool或者@autoreleasepool。建议使用@autoreleasepool,苹果说它NSAutoreleasePool效率更高,并且ARC下只能用autoreleasepool。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
@autoreleasepool blocks are more efficient than using an instance of NSAutoreleasePool directly;
you can also use them even if you do not use ARC.
问:释放NSAutoreleasePool对象,使用[pool release]与[pool drain]的区别?
Objective-C 语言本身是支持 GC 机制的,但有平台局限性,仅限于 MacOS 开发中,iOS 开发用的是 RC 机制。在 iOS 的 RC 环境下[pool release]和[pool drain]效果一样,但在 GC 环境下drain会触发 GC 而release不做任何操作。使用[pool drain]更佳,一是它的功能对系统兼容性更强,二是这样可以跟普通对象的release区别开。(注意:苹果在引入ARC时称,已在 OS X Mountain Lion v10.8 中弃用GC机制,而使用ARC替代)
原理分析
通过macOS工程来分析@autoreleasepool的底层原理。 macOS工程中的main()函数什么都没做,只是放了一个@autoreleasepool。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
通过 Clang clang -rewrite-objc main.m 将以上代码转换为 C++ 代码。
__AtAutoreleasePool
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return NSApplicationMain(argc, argv);
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
@autoreleasepool底层是创建了一个__AtAutoreleasePool结构体对象;
在创建__AtAutoreleasePool结构体时会在构造函数中调用objc_autoreleasePoolPush()函数,并返回一个atautoreleasepoolobj。
在释放__AtAutoreleasePool结构体时会在析构函数中调用objc_autoreleasePoolPop()函数,并将atautoreleasepoolobj传入。
进入Runtime objc4源码查看以上提到的两个函数的实现
AutoreleasePoolPage
// NSObject.mm
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可知,objc_autoreleasePoolPush()和objc_autoreleasePoolPop()两个函数其实是调用了AutoreleasePoolPage类的两个类方法push()和pop()。
所以@autoreleasepool底层就是使用AutoreleasePoolPage类来实现的。
// NSObject.mm
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
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:
// 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 ((AutoreleasePoolPage*)1)
# define POOL_BOUNDARY nil
......
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//开始存储的位置
objc_thread_self(),//当前线程,通过tls获取
newParent,
newParent ? 1+newParent->depth : 0,//如果第一页深度为0,往后是前一个的深度+1
newParent ? newParent->hiwat : 0)
{
......
}
~AutoreleasePoolPage()
{
......
}
......
}
// NSObject-internal.h
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct 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!");
#endif
magic_t const magic;
__unsafe_unretained id *next;
objc_thread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
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内的属性介绍:
magic_t const magic; // 校验AutoreleasePoolPage结构是否完整
__unsafe_unretained id *next; // 指向最新添加的进池的对象的下一个位置,初始时指向begin()
pthread_t const thread; // 指向当前线程
AutoreleasePoolPage * const parent; // 指向父节点,第一个节点的parent值为nil
AutoreleasePoolPage *child; // 指向子节点,最后一个节点的child值为nil
uint32_t const depth; // 代表深度,从0开始,往后递增1
uint32_t hiwat; // 最大入栈数量标记
整个程序运行过程中,可能会有多个AutoreleasePoolPage对象,再结合源码可知:
自动释放池(即所有的AutoreleasePoolPage对象)是以栈为结点通过双向链表的形式组合而成的一种数据结构;
自动释放池与线程一一对应;
每个AutoreleasePoolPage对象占用4096字节内存,其中56个字节用来存放它内部的成员变量,剩下的空间(4040个字节)用来存放autorelease对象的地址。
备注:
56个字节存储的是AutoreleasePoolPageData相关属性。
内存分布图如下:
继续通过源码分析push()、pop()以及autorelease方法的实现。
先来了解一下POOL_BOUNDARY
POOL_BOUNDARY称为哨兵对象或者边界对象;
POOL_BOUNDARY用来区分不同的自动释放池,以解决自动释放池嵌套的问题;
每当创建一个自动释放池,就会调用push()方法将一个POOL_BOUNDARY入栈,并返回其存放的内存地址;
当往自动释放池中添加autorelease对象时,将autorelease对象的内存地址入栈,它们前面至少有一个POOL_BOUNDARY;
当销毁一个自动释放池时,会调用pop()方法并传入一个POOL_BOUNDARY,会从自动释放池中最后一个对象开始,依次给它们发送release消息,直到遇到这个POOL_BOUNDARY。
push
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);
// dtrace probe
OBJC_RUNTIME_AUTORELEASE_POOL_PUSH(dest);
return dest;
}
判断是否有pool page(第一次来肯定是没有的):
如果没有,则通过autoreleaseNewPage创建;
如果有,则通过autoreleaseFast压栈哨兵对象。
autoreleaseFast()
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();//获取当前page
if (page && !page->full()) {//如果当前page存在且未满,则存入当前page
return page->add(obj);
} else if (page) {//如果当前page存在,但满了,则创建新的page,然后存入
return autoreleaseFullPage(obj, page);
} else {//如果当前page不存在,则创建第一个page,并存入
return autoreleaseNoPage(obj);
}
}
autoreleaseFast主要是通过hotPage()获取当前页,判断当前page是否存在:
如果当前page存在且未满,则通过add将autorelease对象入栈;
如果当前page存在且满了,则通过autoreleaseFullPage创建新的页面并存入autorelease对象;
如果当前page不存在,则通过autoreleaseNoPage方法创建新第一个page,并存入autorelease对象。
add
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;
}
page->add(obj)其实就是将autorelease对象添加到Page中的next指针所指向的位置,并将next指针指向这个对象的下一个位置,然后将该对象的位置返回。
autoreleaseFullPage
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);
// dtrace probe
OBJC_RUNTIME_AUTORELEASE_POOL_GROW(page->depth);
return page->add(obj);
}
autoreleaseFullPage()方法中通过do...while循环,通过Page的child指针找到最后一个Page。
如果最后一个Page未满,就通过page->add(obj)将autorelease对象添加到最后一个Page中;
如果最后一个Page已满,就创建一个新的Page并将该Page设置为hotPage,通过page->add(obj)将autorelease对象添加进去。
autoreleaseNoPage
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 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",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
if (DebugMissingPools == Fatal)
_objc_fatal("Missing pools are a fatal error");
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);
// dtrace probe
OBJC_RUNTIME_AUTORELEASE_POOL_GROW(page->depth);
// 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);
}
autoreleaseNoPage()方法中会创建第一个Page。
该方法会判断是否有空的自动释放池存在,如果没有会通过setEmptyPoolPlaceholder()生成一个占位符,表示一个空的自动释放池。
接着创建第一个Page,设置它为hotPage。
最后将一个POOL_BOUNDARY添加进Page中,并返回POOL_BOUNDARY的下一个位置。
push操作的实现,就是往自动释放池中添加一个POOL_BOUNDARY,并返回它存放的内存地址。接着每有一个对象调用autorelease方法,会将它的内存地址添加进自动释放池中。
autorelease
autorelease方法的函数调用栈如下:
// NSObject.mm
1、 objc_autorelease
// objc-object.h
2、 objc_object::autorelease
// NSObject.mm
3、autorelease
4、_objc_rootAutorelease
// objc-object.h
5、objc_object::rootAutorelease
// NSObject.mm
6、objc_object::rootAutorelease2
7、AutoreleasePoolPage::autorelease
AutoreleasePoolPage类的autorelease方法实现如下:
static inline id autorelease(id obj)
{
ASSERT(!_objc_isTaggedPointerOrNil(obj));
id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
ASSERT(!dest || dest == (id *)EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
ASSERT(!dest || dest == (id *)EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endif
return obj;
}
可以看到,调用了autorelease方法的对象,也是通过以上解析的autoreleaseFast()方法添加进Page中。
pop
static inline void
pop(void *token)//
{
......
// 通过token找到所处的page
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
//pool从未被使用,清除占位符。
return 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 {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
拿到哨兵对象所在page后,开始popPage
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);//释放对象
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *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 pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
pop()方法的传参token即为POOL_BOUNDARY对应在Page中的地址。当销毁自动释放池时,会调用pop()方法将自动释放池中的autorelease对象全部释放(实际上是从自动释放池的中的最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY)。pop()方法的执行过程如下:
5、判断token是不是EMPTY_POOL_PLACEHOLDER,是的话就清空这个自动释放池;2、如果不是的话,就通过pageForPointer(token)拿到token所在的Page(自动释放池的首个Page);
3、然后popPage方法内通过调用page->releaseUntil(stop)将自动释放池中的autorelease对象全部释放,传参stop即为POOL_BOUNDARY的地址;
4、判断当前Page是否有子Page,有的话就销毁。
pop()方法中释放autorelease对象的过程在releaseUntil()方法中,下面来看一下这个方法的实现:
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
//从最后一个开始往前
do {
while (this->next != stop) {//
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();//获取当前page
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {//如果当前page空了,则往父节点并更新当前page
page = page->parent;
setHotPage(page);
}
page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
// create an obj with the zeroed out top byte and release that
id obj = (id)entry->ptr;
int count = (int)entry->count; // grab these before memset
#else
id obj = *--page->next;
#endif
memset((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 one
for (int i = 0; i < count + 1; i++) {
objc_release(obj);
}
#else
objc_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
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
releaseUntil()方法其实就是通过一个do...while循环,从最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY。
AutoreleasePoolPage()创建page
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//开始存储的位置
objc_thread_self(),//当前线程
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
newParent ? newParent->hiwat : 0)
{
if (objc::PageCountWarning != -1) {
checkTooMuchAutorelease();
}
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
AutoreleasePoolPage()方法的参数为newParent,新创建的Page的depth加1,next指针的初始位置指向begin,将新创建的Page的parent指针指向newParent。将newParent的child指针指向自己,这就形成了双向链表的结构。
begin、end、empty、full
begin的地址为:Page自己的地址+Page对象的大小56个字节;
end的地址为:Page自己的地址+4096个字节;
empty:判断Page是否为空的条件是next地址是不是等于begin;
full:判断Page是否已满的条件是next地址是不是等于end(栈顶)。
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
总结:
push操作是往自动释放池中添加一个POOL_BOUNDARY,并返回它存放的内存地址;接着每有一个对象调用autorelease方法,会将它的内存地址添加进自动释放池中。
pop操作是传入一个POOL_BOUNDARY的内存地址,从最后一个入栈的autorelease对象开始,将自动释放池中的autorelease对象全部释放(实际上是给它们发送一条release消息),直到遇到这个POOL_BOUNDARY 。
查看自动释放池的情况
使用macOS工程示例分析
结合AutoreleasePoolPage的内存分布图以及_objc_autoreleasePoolPrint()私有函数,来帮助我们更好地理解@autoreleasepool的原理。
注意:
由于ARC环境下不能调用autorelease等方法,所以需要将工程切换为MRC环境。
如果使用ARC,则可以使用__autoreleasing所有权修饰符替代autorelease方法。
单个 @autoreleasepool
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool {
_objc_autoreleasePoolPrint(); // print2
__autoreleasing HFPerson *p1 = [HFPerson new];
__autoreleasing HFPerson *p2 = [HFPerson new];
_objc_autoreleasePoolPrint();//print3
}
_objc_autoreleasePoolPrint();//print4
return NSApplicationMain(argc, argv);
}
@interface HFPerson : NSObject
@end
//自动释放池的情况
objc[14787]: ############## (print1)
objc[14787]: AUTORELEASE POOLS for thread 0x116fbd600
objc[14787]: 0 releases pending. //当前自动释放池没有任何对象
objc[14787]: [0x7fd353009000] ................ PAGE (hot) (cold)
objc[14787]: ##############
objc[14787]: ############## (print2)
objc[14787]: AUTORELEASE POOLS for thread 0x116fbd600
objc[14787]: 1 releases pending. //当前自动释放池中有一个对象,这个对象为POOL_BOUNDARY
objc[14787]: [0x7fd353009000] ................ PAGE (hot) (cold)
objc[14787]: [0x7fd353009038] ################ POOL 0x7fd353009038 //POOL_BOUNDARY
objc[14787]: ##############
objc[14787]: ############## (print3)
objc[14787]: AUTORELEASE POOLS for thread 0x116fbd600
objc[14787]: 3 releases pending.//当前自动释放池中有3个对象:POOL_BOUNDARY、p1、p2
objc[14787]: [0x7fd353009000] ................ PAGE (hot) (cold)
objc[14787]: [0x7fd353009038] ################ POOL 0x7fd353009038 // POOL_BOUNDARY
objc[14787]: [0x7fd353009040] 0x6000020e8140 HFPerson //p1
objc[14787]: [0x7fd353009048] 0x6000020e8110 HFPerson// p2
objc[14787]: ##############
objc[14787]: ############## (print4)
objc[14787]: AUTORELEASE POOLS for thread 0x116fbd600
objc[14787]: 0 releases pending.//当前自动释放池中没有任何对象,因为@autoreleasepool作用域结束,调用pop方法释放了对象
objc[14787]: [0x7fd353009000] ................ PAGE (hot) (cold)
objc[14787]: ##############
嵌套 @autoreleasepool
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { // r1 = push()
_objc_autoreleasePoolPrint(); // print2
__autoreleasing HFPerson *p1 = [HFPerson new];
__autoreleasing HFPerson *p2 = [HFPerson new];
_objc_autoreleasePoolPrint();//print3
@autoreleasepool { // r2 = push
__autoreleasing HFPerson *p3 = [HFPerson new];
_objc_autoreleasePoolPrint();//print4
@autoreleasepool { // r3 = push
__autoreleasing HFPerson *p4 = [HFPerson new];
_objc_autoreleasePoolPrint();//print5
} // pop(r3)
_objc_autoreleasePoolPrint();//print6
}// pop(r2)
_objc_autoreleasePoolPrint();//print7
}//pop(r1)
_objc_autoreleasePoolPrint();//print8
return NSApplicationMain(argc, argv);
}
自动释放池情况
objc[15102]: ############## (print1)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 0 releases pending. //当前自动释放池中没有任何对象
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: ##############
objc[15102]: ############## (print2)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 1 releases pending. //当前自动释放池中有1个对象,这个对象是POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038 //POOL_BOUNDARY
objc[15102]: ##############
objc[15102]: ############## (print3)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 3 releases pending.//当前自动释放池中有3个对象(1个autoreleasepool)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a040] 0x6000010b81a0 HFPerson // p1
objc[15102]: [0x7fbe8a80a048] 0x6000010b81b0 HFPerson // p2
objc[15102]: ##############
objc[15102]: ############## (print4)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 5 releases pending.//当前自动释放池中有5个对象(2个autoreleasepool)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a040] 0x6000010b81a0 HFPerson // p1
objc[15102]: [0x7fbe8a80a048] 0x6000010b81b0 HFPerson // p2
objc[15102]: [0x7fbe8a80a050] ################ POOL 0x7fbe8a80a050 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a058] 0x6000010b81c0 HFPerson // p3
objc[15102]: ##############
objc[15102]: ############## (print5)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 7 releases pending.//当前自动释放池中有7个对象(3个autoreleasepool)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a040] 0x6000010b81a0 HFPerson // p1
objc[15102]: [0x7fbe8a80a048] 0x6000010b81b0 HFPerson // p2
objc[15102]: [0x7fbe8a80a050] ################ POOL 0x7fbe8a80a050 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a058] 0x6000010b81c0 HFPerson // p3
objc[15102]: [0x7fbe8a80a060] ################ POOL 0x7fbe8a80a060 // POOL_BOUNDARY
objc[15102]: [0x7fbe8a80a068] 0x6000010a4010 HFPerson // p4
objc[15102]: ##############
objc[15102]: ############## (print6)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 5 releases pending.//当前自动释放池中有5个对象(第3个@autoreleasepool已释放)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038
objc[15102]: [0x7fbe8a80a040] 0x6000010b81a0 HFPerson
objc[15102]: [0x7fbe8a80a048] 0x6000010b81b0 HFPerson
objc[15102]: [0x7fbe8a80a050] ################ POOL 0x7fbe8a80a050
objc[15102]: [0x7fbe8a80a058] 0x6000010b81c0 HFPerson
objc[15102]: ##############
objc[15102]: ############## (print7)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 3 releases pending.//当前自动释放池中有5个对象(第2、3个@autoreleasepool已释放)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: [0x7fbe8a80a038] ################ POOL 0x7fbe8a80a038
objc[15102]: [0x7fbe8a80a040] 0x6000010b81a0 HFPerson
objc[15102]: [0x7fbe8a80a048] 0x6000010b81b0 HFPerson
objc[15102]: ##############
objc[15102]: ############## (print8)
objc[15102]: AUTORELEASE POOLS for thread 0x10d7a4600
objc[15102]: 0 releases pending.//当前自动释放池没有任何对象(3个@autoreleasepool都已释放)
objc[15102]: [0x7fbe8a80a000] ................ PAGE (hot) (cold)
objc[15102]: ##############
复杂情况 @autoreleasepool
由AutoreleasePoolPage类的定义可知,自动释放池(即所有的AutoreleasePoolPage对象)是以栈为结点通过双向链表的形式组合而成。
每当Page满了的时候,就会创建一个新的Page,并设置它为hotPage,而首个Page为coldPage。
接下来我们来看一下多个Page和多个@autoreleasepool嵌套的情况。
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
for (int i = 0; i < 600; i++) {
__autoreleasing HFPerson *p = [HFPerson new];
}
@autoreleasepool { // r2 = push
for (int i = 0; i < 500; i++) {
__autoreleasing HFPerson *p = [HFPerson new];
}
@autoreleasepool { // r3 = push
for (int i = 0; i < 200; i++) {
__autoreleasing HFPerson *p = [HFPerson new];
}
_objc_autoreleasePoolPrint();
}// pop(r3)
}// pop(r2)
}//pop(r1)
return NSApplicationMain(argc, argv);
}
自动释放池情况
objc[15261]: ##############
objc[15261]: AUTORELEASE POOLS for thread 0x10cdb5600
objc[15261]: 1303 releases pending.
objc[15261]: [0x7fda05012000] ................ PAGE (full) (cold)
objc[15261]: [0x7fda05012038] ################ POOL 0x7fda05012038 //POOL_BOUNDARY
objc[15261]: [0x7fda05012040] 0x600002acc170 HFPerson //p1
objc[15261]: [0x7fda05012048] ..................... //...
objc[15261]: [0x7fda05012ff8] 0x600002ace040 HFPerson // p504
objc[15261]: [0x7fda0500a000] ................ PAGE (full) //第二个page
objc[15261]: [0x7fda0500a038] 0x600002ace050 HFPerson // p505
objc[15261]: [0x7fda05012048] ..................... //...
objc[15261]: [0x7fda0500a330] 0x600002ace640 HFPerson // p600
objc[15261]: [0x7fda0500a338] ################ POOL 0x7fda0500a338 //POOL_BOUNDARY
objc[15261]: [0x7fda0500a340] 0x600002ace650 HFPerson // 601
objc[15261]: [0x7fda05012048] ..................... //...
objc[15261]: [0x7fda0500aff8] 0x600002acffc0 HFPerson //p1008
objc[15261]: [0x7fda0500c000] ................ PAGE (hot) //第三个page
objc[15261]: [0x7fda0500c038] 0x600002acffd0 HFPerson // p1009
objc[15261]: [0x7fda05012048] ..................... //...
objc[15261]: [0x7fda0500c310] 0x600002adc580 HFPerson //p1100
objc[15261]: [0x7fda0500c318] ################ POOL 0x7fda0500c318 //POOL_BOUNDARY
objc[15261]: [0x7fda0500c320] 0x600002adc590 HFPerson //p1101
objc[15261]: [0x7fda05012048] ..................... //...
objc[15261]: [0x7fda0500c958] 0x600002add200 HFPerson // p1300
根据输出的自动释放池使用情况可知:
一个AutoreleasePoolPage对象的内存大小为4096个字节,它自身成员变量占用内存56个字节,所以剩下的4040个字节用来存储autorelease对象的内存地址。
又因为64bit下一个OC对象的指针所占内存为8个字节,所以一个Page可以存放505个对象的地址。
POOL_BOUNDARY也是一个对象,所以存放POOL_BOUNDARY后,此页可以放504个对象。
以上代码的自动释放池内存分布图如下所示:
从以上macOS工程示例可以得知:
在@autoreleasepool大括号结束的时候,就会调用Page的pop()方法,给@autoreleasepool中的autorelease对象发送release消息。
使用iOS工程示例分析
iOS工程中,方法里的autorelease对象是什么时候释放的呢?
有系统干预释放和手动干预释放两种情况。
系统干预释放是不指定@autoreleasepool,所有autorelease对象都由主线程的RunLoop创建的@autoreleasepool来管理。
手动干预释放就是将autorelease对象添加进我们手动创建的@autoreleasepool中。
系统干预释放
- (void)viewDidLoad {
[super viewDidLoad];
// MRC
HFPerson *person = [[[HFPerson alloc] init] autorelease];
//ARC
//__autoreleasing HFPerson *p1 = [HFPerson new];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
@implementation HFPerson
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
打印输出
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[HFPerson dealloc]
-[ViewController viewDidAppear:]
可以看到,调用了autorelease方法的person对象不是在viewDidLoad方法结束后释放,而是在viewWillAppear方法结束后释放,说明在viewWillAppear方法结束的时候,调用了pop()方法释放了person对象,这是由RunLoop控制的。
RunLoop和@autoreleasepool
iOS在主线程的RunLoop中注册了两个Observer。
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush();
第2个Observer
1、 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush();
2、监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。
在iOS工程中系统干预释放的autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放。
示例中person对象在viewWillAppear方法结束后释放,说明viewDidLoad和viewWillAppear方法在同一次循环里。
kCFRunLoopEntry:在即将进入RunLoop时,会自动创建一个__AtAutoreleasePool结构体对象,并调用objc_autoreleasePoolPush()函数。
kCFRunLoopBeforeWaiting:在RunLoop即将休眠时,会自动销毁一个__AtAutoreleasePool对象,调用objc_autoreleasePoolPop()。然后创建一个新的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPush()。
kCFRunLoopBeforeExit,在即将退出RunLoop时,会自动销毁最后一个创建的__AtAutoreleasePool对象,并调用objc_autoreleasePoolPop()。
手动干预释放
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
// MRC
HFPerson *person = [[[HFPerson alloc] init] autorelease];
//ARC
//__autoreleasing HFPerson *p1 = [HFPerson new];
}
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
打印输出
-[HFPerson dealloc]
-[ViewController viewDidLoad]
-[ViewController viewWillAppear:]
-[ViewController viewDidAppear:]
可以看到,添加进手动指定的@autoreleasepool中的autorelease对象,在@autoreleasepool大括号结束时就会释放,不受RunLoop控制。
相关问题
1、ARC 环境下,autorelease 对象在什么时候释放?
系统干预释放:autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放.
手动干预释放:添加到指定的@autoreleasepool中的autorelease对象,在@autoreleasepool大括号结束时就会释放,不受RunLoop控制。
2、ARC 环境下,需不需要手动添加 @autoreleasepool?
通常情况下不必自己创建@autoreleasepool,但是如果需要在循环中创建很多临时的autorelease对象时,需要手动添加@autoreleasepool。
苹果给出了三种需要手动添加@autoreleasepool的情况:
1、如果编写的程序不是基于 UI 框架的,比如说命令行工具;
2、如果你编写的循环中创建了大量的临时对象;
可以在循环内使用@autoreleasepool在下一次迭代之前处理这些对象。在循环中使用@autoreleasepool有助于减少应用程序的最大内存占用。
3、 创建了辅助线程。
一旦线程开始执行,就必须创建自己的@autoreleasepool;否则,你的应用程序将存在内存泄漏。
3、如果对 NSAutoreleasePool 对象调用 autorelease 方法会发生什么情况?
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool autorelease];
//输出
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[NSAutoreleasePool autorelease]: Cannot autorelease an autorelease pool'
抛出异常NSInvalidArgumentException并导致程序Crash,异常原因:不能对NSAutoreleasePool对象调用autorelease。
4、AutoreleasePool的原理
自动释放池是以栈为结点通过双向链表的形式组合而成的一种数据结构。它的本质是一个AutoreleasePoolPage结构体对象。
自动释放池的进栈和出栈主要是通过结构体的构造函数和析构函数调用底层objc_autoreleasePoolPush和objc_autoreleasePoolPop,实际上是调用AutoreleasePoolPage的push和pop两个方法。
push操作就是获取到当前AutoreleasePoolPage,然后插入一个哨兵对象POOL_BOUNDARY,并返回哨兵对象POOL_BOUNDARY的内存地址。
关键操作是push内调用autoreleaseFast方法:
当page存在且不满时,调用add方法将对象添加至page的next指针处,并将next指针指向下一位。
当page存在,且已满时,调用autoreleaseFullPage初始化一个新的page,然后调用add方法将对象添加至这个page中。
若page不存在,调用autoreleaseNoPage,创建一个hotpage,然后调用add方法将对象添加至这个page中。pop操作,需要一个参数,这个参数就是push操作返回的值,即哨兵对象POOL_BOUNDARY的内存地址token。pop根据token找到哨兵对象POOL_BOUNDARY所处的page,然后使用objc_release释放token之前的对象,并将next指到正确的位置。
5、一个自动释放池内有几个哨兵对象?
一个自动释放池内有一个哨兵对象POOL_BOUNDARY
6、AutoreleasePool能否嵌套使用?
可以嵌套使用,其目的是可以控制应用程序的内存峰值,使其不要太高;
可以嵌套的原因是因为自动释放池是以栈为节点,通过双向链表的形式连接的,且是和线程一一对应的;
自动释放池的多层嵌套其实就是不停的push哨兵对象,在pop时,会先释放里面的,再释放外面的。
7、哪些对象可以加入AutoreleasePool?alloc创建的对象可以吗?
使用alloc、new、copy、mutableCopy、allocWith...生成的对象和retain了的对象需要手动释放或arc自动管理,不会被添加到自动释放池中;
但是被__autoreleasing修饰/autorelease调用则会被添加到自动释放池中;
所有 autorelease的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。
8、线程和@autoreleasepool的关系
官方文档中,提到:
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.
每个线程都有与之关联的自动释放池堆栈结构,新的pool在创建时会被压栈到栈顶,pool销毁时,会被出栈,对于当前线程来说,释放对象会被压栈到栈顶,线程停止时,会自动释放与之关联的自动释放池。
转载参考:
iOS - 聊聊 autorelease 和 @autoreleasepool
iOS 内存管理底层分析(二)- AutoreleasePool底层
其他相关链接
autoreasepool 官方文档