- 新建一个命令行工程,并将main.m文件编译成main.cpp文件,cd到工程路径,并输入命令
clang -rewrite-objc main.m -o main.cpp
- 可以看到自动释放池
@autoreleasepool {}
,在底层是一个__AtAutoreleasePool
类型的结构体;结构如下所示:
struct __AtAutoreleasePool {
//构造函数
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush()
}
//析构函数
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
- 通过汇编分析,在main函数中加入断点,并打开汇编代码分析界面如下所示:
- 看到自动释放池,在入口开始时,加入对象,底层调用
objc_autoreleasePoolPush
函数,在出口结束时,释放对象,底层调用objc_autoreleasePoolPop
函数; - 在进行autoreleasePool源码分析之前,首先来介绍一个重要的数据结构
AutoreleasePoolPage
,autoreleasePool就是由多个AutoreleasePoolPage分页组成的双向链表结构
,AutoreleasePoolPage
,源码如下:
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:
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
static size_t const MAX_FAULTS = 2;
// 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
// SIZE-sizeof(*this) bytes of contents follow
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);
}
inline void protect() {
#if PROTECT_AUTORELEASEPOOL
mprotect(this, SIZE, PROT_READ);
check();
#endif
}
inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
check();
mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
}
void checkTooMuchAutorelease(){}
//构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0) {}
//析构函数
~AutoreleasePoolPage() {}
//页的数据存储是以栈的形式存储外界对象的
//页的开始位置
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();
}
//当前页是否存储对象小于一半
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
//往页中添加对象
id *add(id obj){}
//释放所有对象
void releaseAll()
{
releaseUntil(begin());
}
//释放到stop位置之前的所有对象
void releaseUntil(id *stop) {}
//杀掉
void kill() {}
//释放本地线程存储空间
static void tls_dealloc(void *p) {}
//获取AutoreleasePoolPage
static AutoreleasePoolPage *pageForPointer(const void *p) {}
static AutoreleasePoolPage *pageForPointer(uintptr_t p) {}
//是否有空池占位符
static inline bool haveEmptyPoolPlaceholder(){ }
//设置空池占位符
static inline id* setEmptyPoolPlaceholder(){}
//获取当前操作页
static inline AutoreleasePoolPage *hotPage() {}
//将page设置为当前操作页
static inline void setHotPage(AutoreleasePoolPage *page){}
//获取coldPage -- 也就是首页即第一页
static inline AutoreleasePoolPage *coldPage() {}
//快速入栈 加入当前页
static inline id *autoreleaseFast(id obj){}
//往page中添加对象,并检测page是否已满
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page){}
//pool为空时创建新的一页
static __attribute__((noinline))
id *autoreleaseNoPage(id obj){}
//创建新的一页
static __attribute__((noinline))
id *autoreleaseNewPage(id obj){}
public:
//将obj加入自动释放池,入栈,加入当前页
static inline id autorelease(id obj){}
//入栈
static inline void *push() {}
__attribute__((noinline, cold))
static void badPop(void *token){}
//出栈
template
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop){}
__attribute__((noinline, cold))
static void
popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){}
//出栈
static inline void pop(void *token){}
static void init(){}
__attribute__((noinline, cold))
void print(){}
__attribute__((noinline, cold))
static void printAll(){ }
__attribute__((noinline, cold))
static void printHiwat() {}
- AutoreleasePoolPage是继承自
AutoreleasePoolPageData
,其结构如下:
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 = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
//用来校验AutoreleasePoolPage的结构是否完整
magic_t const magic;
//指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
__unsafe_unretained id *next;
//指向当前线程
pthread_t const thread;
//指向父节点,第一个结点的parent值为nil
AutoreleasePoolPage * const parent;
//指向子节点,最后一个结点的child值为nil
AutoreleasePoolPage *child;
//表示深度,从0开始,往后递增1
uint32_t const depth;
//表示high water mark 最大入栈数量标记
uint32_t hiwat;
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)
{
}
};
- 其中AutoreleasePoolPageData结构体的内存大小为
56字节
; - 属性
magic
的类型是magic_t结构体,所占内存大小为m[4],所占内存(即4*4=16字节) - 属性
next(指针)、thread(对象)、parent(对象)、child(对象)
均占8字节(即4*8=32字节); - 属性
depth、hiwat
类型为uint32_t,实际类型是unsigned int类型,均占4字节(即2*4=8字节) -
next
是指当前页存储的最新的对象的下一个地址; -
parent
是指当前页的上一页; -
child
是指当前页的下一页;
-
id * begin()
:获取当前页存储外界对象的栈底指针,在其内部加入断点,调试结构如下:
- 表明AutoreleasePoolPage内部成员占用了56个字节,剩余的内存空间才是用来存储autorelease对象的;
查看自动释放池的内存结构
- 将工程配置成MRC环境,main.m文件中加入以下代码:
#import
#import "YYPerson.h"
//************打印自动释放池结构************
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//循环创建对象,并加入自动释放池
for (int i = 0; i < 5; i++) {
NSObject *objc = [[NSObject alloc] autorelease];
}
//调用
_objc_autoreleasePoolPrint();
}
return 0;
}
-
调试结果如下:
-
AutoreleasePoolPage
栈从0x102013038
开始,栈底第一个元素存储的是哨兵对象
,其中的POOL表示哨兵,即边界,其目的是为了防止越界; -
AutoreleasePoolPage
内部地址从0x102013000
开始,到栈底相隔0x38,换算成10进制即56,再次证明AutoreleasePoolPage内部成员占用了56个字节; - 将for循环中的i改成505,调试结果如下:
- 看到第一页存储了504个NSObject + 1个哨兵对象;占4040个字节,再加上本身的成员占用的56个字节,所以一个
AutoreleasePoolPage的存储空间大小为4096个字节
- 当第一页存满了,会创建新的一页即第二页,存储了一个NSObject,可以看到
只有当第一页的时候才会存入一个哨兵对象,后面的都不会存入哨兵对象
;所以从第二页开始会存入505个NSObject;
objc_autoreleasePoolPush函数分析
- 源码如下:
void * _objc_autoreleasePoolPush(void){
return objc_autoreleasePoolPush();
}
void * objc_autoreleasePoolPush(void){
return AutoreleasePoolPage::push();
}
//入栈
static inline void *push()
{
id *dest;
//判断是否存在pool
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
//若没有,创建一个新的AutoreleasePoolPage,作为新的pool的起点
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
-
slowpath(DebugPoolAllocation)
:判断是否存在自动释放池,自动释放池都是以一个AutoreleasePoolPage
为起点的; - 1.如果没有,执行
autoreleaseNewPage(POOL_BOUNDARY)
,传入一个边界对象(哨兵对象),源码如下:
//创建新的一页
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
//获取当前的操作页
AutoreleasePoolPage *page = hotPage();
//若当前操作页存在,将obj压栈
if (page) return autoreleaseFullPage(obj, page);
//若当前操作页不存在,创建一个新的操作页
else return autoreleaseNoPage(obj);
}
-
AutoreleasePoolPage *page = hotPage()
:获取当前的操作页;若当前操作页存在,将obj压栈,执行autoreleaseFullPage(obj, page)
;若当前操作页不存在,则创建一个新的操作页,执行autoreleaseNoPage(obj)
;
//往page中添加对象,并检测page是否已满
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());
//设置page为当前操作页
setHotPage(page);
//将对象压栈到当前操作页
return page->add(obj);
}
- 当当前页已满,执行
if (page->child) page = page->child
//pool为空时创建新的一页
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);
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.
//创建pool的第一页
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);
}
-
haveEmptyPoolPlaceholder()
:判断自动释放池,是否存在空占位符,如果存在则表明自动释放池中没有任何分页,那么在创建第一个分页时,可压栈一个哨兵对象。 -
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil)
:创建pool的第一页,调用的是AutoreleasePoolPage的构造方法;
//构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (objc::PageCountWarning != -1) {
checkTooMuchAutorelease();
}
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
- 2.自动释放池存在,也就是说至少存在一个分页
AutoreleasePoolPage
,将POOL_BOUNDARY快速入栈,执行autoreleaseFast(POOL_BOUNDARY)
//快速入栈 加入当前页
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);
}
}
-
page->add(obj)
:往当前页中加入autorelease对象;
//往页中添加对象
id *add(id obj)
{
ASSERT(!full());
unprotect();
......
id *ret;
ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
// Make sure obj fits in the bits available for it
ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
#endif
done:
protect();
return ret;
}
-
*next++ = obj
:next指针指向当前最新添加对象的下一个地址;
autorelease源码分析
- MRC环境,工程代码如下:
#import
#import "YYPerson.h"
//************打印自动释放池结构************
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[NSObject alloc] autorelease];
//调用
_objc_autoreleasePoolPrint();
}
return 0;
}
- 底层源码调用流程图如下所示:
- 核心代码,底层调用
AutoreleasePookPage :: autorelease
函数
//自动释放对象入栈
static inline id autorelease(id obj)
{
ASSERT(!obj->isTaggedPointerOrNil());
id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endif
return obj;
}
objc_autoreleasePoolPop函数分析
- 其具体的实现流程如下图所示:
//出栈
static inline void pop(void *token){
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.
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(token, page, stop);
}
- 自动释放池pop所有对象,传入的参数一般是push函数返回的对象,也就是是第一页的第一个对象即token;所有会执行
page = pageForPointer(token)
,当前页page为第一页,stop = token,然后执行popPage
;(token, page, stop)
//出栈
template
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//当前页 释放从next到栈底之间的所有对象
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();
}
}
}
-
page->releaseUntil(stop)
:将当前页的从next到栈底之间的所有对象全部释放; - 参数
allowDebug = false
,所以直接进入最后的判断逻辑,当前页有子节点,且内部元素对象少于一半时,将其子节点kill掉,注意是所有子节点,只保留当前节点页(且是空页);
//杀掉
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
- 循环kill掉所有子节点页;