手动目录
- 初探autoreleasePool结构
- AutoreleasePoolPage 结构
- 每个Page能装多少个对象
- push & pop
- 面试题
初探autoreleasePool结构
用clang的方式探索autoreleasePool
int main(int argc, char * argv[]){
@autoreleasepool {
}
return 1;
}
用clang命令:clang -rewrite-objc main.m
之后得到.cpp文件
int main(int argc, char * argv[]){
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 1;
}
关键就在{ __AtAutoreleasePool __autoreleasepool; }
,在.cpp 文件中 找到其结构
// 是一个结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} // 构造函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} // 析构函数
void * atautoreleasepoolobj;
};
在这个结构体中,它调用了一个objc_autoreleasePoolPush
方法 ,那么它到底干了什么,我们在objc源码中去看
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push(); // 压栈
}
class AutoreleasePoolPage : private AutoreleasePoolPageData
AutoreleasePoolPage 结构
找到这里的时候,我们先来看看AutoreleasePoolPage
的说明
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、stack
先进后出
2、pool boundary
边界(也有叫哨兵的),它是一个对象,可以理解为标记。
3、释放池里存的 要么是边界,要么是需要释放的对象。
4、是一个双向链表结构
继续跟踪AutoreleasePoolPage
的父类AutoreleasePoolPageData
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic; // 检查校验完整性的变量 16位
__unsafe_unretained id *next; // 指向新加入的autorelease对象下一个节点 8
pthread_t const thread; // 当前所在的线程 8
AutoreleasePoolPage * const parent; // 父节点 指向前一个page 8
AutoreleasePoolPage *child; // 子节点 指向下一个page 8
uint32_t const depth; // 链表的深度,节点个数 4
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)
{
}
};
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1; // 全局静态 在常量区 .data段 不占这里的位数
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12; // 全局静态 在常量区 .data段 不占这里的位数
uint32_t m[4]; // 4 * 4 = 16 这才是为什么这个结构体16位
......
};
相关属性说明
-
magic
检查校验完整性的变量 -
next
指向新加入的autorelease对象下一个位置 -
thread
page当前所在的线程 -
parent
父节点 指向前一个page -
child
子节点 指向下一个page -
depth
链表的深度,节点个数 -
hiwat
数据容纳的一个上限
链表的结构大概为:
AutoreleasePoolPage
每个Page能装多少个对象
我们通过源码来找:
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
begin 从 sizeof(this))开始 (上面计算了它的大小 56) 到 SIZE结束
SIZE = PAGE_MIN_SIZE = PAGE_SIZE = 4096;
那么能存的对象数量是:(4096 - 56)/8 = 505
*
打印来验证
在可运行的objc源码中, 修改setting -> automatic reference counting = NO
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (NSInteger i = 0; i < 5; i ++) {
NSObject *obj = [[NSObject alloc] autorelease];
}
_objc_autoreleasePoolPrint();
return 0;
}
}
// 打印结果
1objc[65796]: ##############
objc[65796]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[65796]: 6 releases pending. // 这里说了 添加了6个
objc[65796]: [0x10181d000] ................ PAGE (hot) (cold)
objc[65796]: [0x10181d038] ################ POOL 0x10181d038
objc[65796]: [0x10181d040] 0x10103d480 NSObject
....
objc[65796]: ##############
// 上面的循环换成 1 + 504
1objc[66085]: ##############
objc[66085]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[66085]: 506 releases pending. // 这里说了 506
objc[66085]: [0x101809000] ................ PAGE (full) (cold) // 注意这里: page full(满了)
objc[66085]: [0x101809038] ################ POOL 0x101809038
objc[66085]: [0x101809040] 0x10104af30 NSObject
....
objc[66085]: [0x101809ff8] 0x10104f6e0 NSObject
objc[66085]: [0x10180b000] ................ PAGE (hot) // 这里也换了一页
objc[66085]: [0x10180b038] 0x10104f6f0 NSObject
objc[66085]: ##############
KCObjcTest was compiled with optimization - stepping may behave oddly; variables may not be available.
通过更多的打印,我们发现
每页可以添加505个对象
但是第一页实际只加进去504,因为第一页有一个边界对象
。
后面的每页可以实际加入505个对象
push & pop
先看源码 如何将对象加入进去
在看源码之前先了解hotPage是当前页 对看源码有帮助
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
// push
static inline void *push()
{
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 == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
后面的 add() 、autoreleaseFullPage()、autoreleaseNoPage()可以继续往下跟。代码太长,就不贴出来了
pop
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
pop(void *token) {}
popPage()
// 具体代码就不贴了。
压栈过程 (push)
1、如果有分页,并且这个页还没有满,直接add
2、如果有分页,并且满了,创建一张新页便加入对象。
3、如果还没有页,创建一个page 并加入边界对象和要加入的对象。出栈(pop)
1、通过while循环,从后往前找到所有page的对象,并进行release操作
2、删除所有的空表
autoreleasePool 嵌套
嵌套使用autoreleasePool的情况下,会发生什么?
我们用代码来打印
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
_objc_autoreleasePoolPrint();
}
}
}
return 0;
}
// 打印结果
objc[70971]: ##############
objc[70971]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[70971]: 6 releases pending.
objc[70971]: [0x10080c000] ................ PAGE (hot) (cold)
objc[70971]: [0x10080c038] ################ POOL 0x10080c038
objc[70971]: [0x10080c040] 0x100683ca0 NSObject
objc[70971]: [0x10080c048] ################ POOL 0x10080c048
objc[70971]: [0x10080c050] 0x100683e40 NSObject
objc[70971]: [0x10080c058] ################ POOL 0x10080c058
objc[70971]: [0x10080c060] 0x100683b90 NSObject
objc[70971]: ##############
在看另外一种打印
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"--------objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 子线程有一个隐性的autoreleasepool
@autoreleasepool {
sleep(1);
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"+++++++obj = %@",obj);
_objc_autoreleasePoolPrint();
}
});
_objc_autoreleasePoolPrint();
}
sleep(2); // 防止子线程没有执行完
return 0;
}
// 打印结果
Test[74472:1192459] --------objc = <NSObject: 0x10290d0b0>
objc[74472]: ############## // 第一个autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x1000d5dc0 // 依赖的线程不同
objc[74472]: 2 releases pending.
objc[74472]: [0x10300a000] ................ PAGE (hot) (cold)
objc[74472]: [0x10300a038] ################ POOL 0x10300a038
objc[74472]: [0x10300a040] 0x10290d0b0 NSObject
objc[74472]: ##############
KCObjcTest[74472:1192759] +++++++obj = <NSObject: 0x102804ac0>
objc[74472]: ############## // 第二个autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x70000b669000
objc[74472]: 3 releases pending.
objc[74472]: [0x10380c000] ................ PAGE (hot) (cold)
objc[74472]: [0x10380c038] ################ POOL 0x10380c038 // 一个隐性autoreleasepool
objc[74472]: [0x10380c040] ################ POOL 0x10380c040 //手动添加的autoreleasepool
objc[74472]: [0x10380c048] 0x102804ac0 NSObject
objc[74472]: ##############
- 1、相同的线程,嵌套autoreleasePool 用的是相同的page,但是每个autoreleasepool 都会添加一个边界对象(哨兵)
- 2、 不同的线程 使用不同的autoreleasePool
面试题
-
1、ARC 下什么样的对象由 Autoreleasepool 管理
当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release)不是通过alloc等出来的对象 会加入自动释放池 比如
id obj = [NSMutableArray array];
-
2、Autoreleasepool 与 Runloop 的关系
主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理
-
3、子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?
在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,
网友评论