-
Notifications
You must be signed in to change notification settings - Fork 53
自动释放池
int main(int argc, const char * argv[]) {
@autoreleasepool {
printf("Hello, World!\n");
}
return 0;
}
使用 clang -rewrite-objc main.m
让编译器重写后结果如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
printf("Hello, World!\n");
}
return 0;
}
@autoreleasepool{}
作用实际相当于
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// other code...
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
分别调用了 AutoreleasePoolPage
的静态方法 push
和 pop
。
//objc_autoreleasePoolPush
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//objc_autoreleasePoolPop
NEVER_INLINE void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
-
一个线程的自动释放池是一个堆栈,用来存放待释放的对象的指针。 A thread's autorelease pool is a stack of pointers.
-
每个指针要么是待释放的对象,要么是
POOL_BOUNDARY
,它是一个自动释放的池边界。 Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. -
池令牌是指向该池的
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.
#define PAGE_MIN_SIZE (1 << 12)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t; //这里使用了友元结构体,不甚也解该语法,暂时略过,有时间再看!
public:
static size_t const SIZE = PAGE_MIN_SIZE; //size and alignment, power of 2
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);
}
AutoreleasePoolPage
看着与线程有关,暂时不关注,以后再研究。AutoreleasePoolPage
的大小为 1 << 12
,正好 4KB,即 iOS 中最小页的大小!
In OS X and in earlier versions of iOS, the size of a page is 4 kilobytes. In later versions of iOS, A7- and A8-based systems expose 16-kilobyte pages to the 64-bit userspace backed by 4-kilobyte physical pages, while A9 systems expose 16-kilobyte pages backed by 16-kilobyte physical pages!
iPhone7 使用 frida 测试页大小为 16KB
[iPhone::App Store]-> console.log(Process.pageSize) //输出结果:16384
AutoreleasePoolPage
继承自 AutoreleasePoolPageData
,实现如下:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
//AutoreleasePoolPageData 构造方法,新构建的自动释放池子页面为 nil
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
内存结果示意图:
magic_t
为 AutoreleasePoolPageData
魔数,用于标识页面
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
magic_t() {
m[0] = M0;
strncpy((char *)&m[1], M1, M1_len);
}
bool check() const {
return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
}
bool fastcheck() const {
return (m[0] == M0);
}
# undef M1
};
AutoreleasePoolPage
构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
parent->unprotect();
parent->child = this; //连接子页面
parent->protect();
}
protect();
}
AutoreleasePoolPage
初始化
// App 启动,首次初始化时,父页面为 `nil`
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// 非首次初始化,需要传入父页面
page = new AutoreleasePoolPage(parentPage);
AutoreleasePoolPage
重载了 new
运算符,new 新的对象时分配额外内存,大小为 PAGE_MIN_SIZE(1<<12)。
//初始化分配内存,
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);
}
使用 sizeof
打印 AutoreleasePoolPage
大小,得到的内存大小不是 4096
,而是 56
,也就是 AutoreleasePoolPageData
的内部变量占用的内存大小。
(lldb) p sizeof(AutoreleasePoolPage)
(unsigned long) $2 = 56
//begin 方法
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
//end 方法cd
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
AutoreleasePoolPage
初始化时,状态如下图所示,parent 指向父页面,child 为 nil,next
指向 begin()
函数指向的位置。
当 next
指针与 begin()
函数指向相同位置时,说明当前页面为一个空页面。
bool empty() {
return next == begin();
}
当 next
与 end()
指向同一位置时,说明当前页面已满,需要重新创建新的页面。
bool full() {
return next == end();
}
id *add(id obj)
{
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
向自动释放池添加对象时,移动 next
指针,如下图所示:
hotPage
指当前正在使用的页面
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
coldPage
为父页面为 nil
的页面,也就是第一页。
static inline AutoreleasePoolPage *coldPage()
{
AutoreleasePoolPage *result = hotPage();
if (result) {
while (result->parent) {
result = result->parent;
result->fastcheck();
}
}
return result;
}
来看一下 AutoreleasePoolPage::push()
方法的真面目:
static inline void *push()
{
id *dest = autoreleaseFast(POOL_BOUNDARY);
return dest;
}
push
方法中主要调用了 autoreleaseFast
方法,并且把 POOL_BOUNDARY
添加到自动释放池,POOL_BOUNDARY
为自动释放池的边界,实际上是一个 nil
对象。
# define POOL_BOUNDARY nil
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);
}
}
首先获取当前 hotPage
,接着分以下三种情形执行:
-
如果
hotPage
不为 nil,并且没有被填满,直接将对象添加到自动释放池(参见上面 add 方法)。 -
如果
hotPage
不为 nil,并且已被填满,则重新创建新的页面,并添加到当前页面链表上,最后同样调用add
方法把obj
添加到新创建的页面中。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. do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); }
-
如果不存在
hotPage
,则调用autoreleaseNoPage
,创建新页面,根据需要设置页面边界,最后添加obj
对象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 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 && !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); }
static inline void
pop(void *token)
{
AutoreleasePoolPage *page = pageForPointer(token);
id *stop = (id *)token;
return popPage(token, page, stop);
}
pageForPointer
根据传入的 token
获取自动释放池页面
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
popPage
实现主要代码如下:
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
page->releaseUntil(stop);
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();
}
}
}
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
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();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
}
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);
}
给一个对象发送 autorelease
方法,调用堆栈如下:
//这里直接摘自 draveness【自动释放池的前世今生.md】一文,https://git.io/Jta1i
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└── id objc_object::rootAutorelease2()
└── static id AutoreleasePoolPage::autorelease(id obj)
└── static id AutoreleasePoolPage::autoreleaseFast(id obj)
├── id *add(id obj)
├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
│ ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
│ └── id *add(id obj)
└── static id *autoreleaseNoPage(id obj)
├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
└── id *add(id obj)
autorelease
方法中调用了 _objc_rootAutorelease
方法
//NSObject.mm
- (id)autorelease {
return _objc_rootAutorelease(self);
}
_objc_rootAutorelease
实现如下:
_objc_rootAutorelease(id obj)
{
return obj->rootAutorelease();
}
objc_object
结构体中,rootAutorelease
方法调用 rootAutorelease2
方法
// Base autorelease implementation, ignoring overrides.
inline id objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
rootAutorelease2
调用了 AutoreleasePoolPage::autorelease
方法
id
objc_object::rootAutorelease2()
{
return AutoreleasePoolPage::autorelease((id)this);
}
AutoreleasePoolPage::autorelease(id)
方法,最终调用 autoreleaseFast
方法,将对象加入自动释放池
static inline id AutoreleasePoolPage::autorelease(id obj)
{
id *dest __unused = autoreleaseFast(obj);
return obj;
}
调用 Runtime 私有方法 _objc_autoreleasePoolPrint()
打印全部自动释放池信息
(lldb) po _objc_autoreleasePoolPrint();
输出结果如下:
objc[27435]: ##############
// 自动释放池与线程有关
objc[27435]: AUTORELEASE POOLS for thread 0x10b5eddc0
objc[27435]: 20 releases pending. // 池中有 20 个待释放的对象,三个页面
//第一个自动释放池页面,coldPage
objc[27435]: [0x7fb86d85c000] ................ PAGE (cold)
objc[27435]: [0x7fb86d85c038] ################ POOL 0x7fb86d85c038 //自动释放池边界
objc[27435]: [0x7fb86d85c040] 0x600003758a40 _UIBoxedAutoreleasePoolMark
//第二个自动释放池页面
objc[27435]: [0x7fb86d85e000] ................ PAGE
objc[27435]: [0x7fb86d85e038] ################ POOL 0x7fb86d85e038 //自动释放池边界
//第三个自动释放池页面,hotPage
objc[27435]: [0x7fb86d860000] ................ PAGE (hot)
objc[27435]: [0x7fb86d860038] ################ POOL 0x7fb86d860038
objc[27435]: [0x7fb86d860040] 0x600000e58750 UITraitCollection autorelease count 3
objc[27435]: [0x7fb86d860048] 0x7fb86d406580 UIWindowScene
objc[27435]: [0x7fb86d860050] 0x7fb86e81d000 UINavigationController autorelease count 13
objc[27435]: [0x7fb86d860058] 0x7fb86d6083d0 UIWindow autorelease count 7
objc[27435]: [0x7fb86d860060] 0x60000114e640 NSURL
objc[27435]: [0x7fb86d860068] 0x60000095b300 __NSCFString
objc[27435]: [0x7fb86d860070] 0x600003510fe0 NSPathStore2
objc[27435]: [0x7fb86d860078] 0x60000114e3a0 NSURL
objc[27435]: [0x7fb86d860080] 0x600000c54380 __NSCFString
objc[27435]: [0x7fb86d860088] 0x600003b572a0 __NSArrayM
objc[27435]: [0x7fb86d860090] 0x600003b55a70 __NSArrayM
objc[27435]: [0x7fb86d860098] 0x7fb86e81d000 UINavigationController autorelease count 2
objc[27435]: [0x7fb86d8600a0] 0x7fb86d6083d0 UIWindow autorelease count 4
objc[27435]: [0x7fb86d8600a8] 0x600003b56d30 __NSArrayM
objc[27435]: [0x7fb86d8600b0] 0x600003b0b450 __NSArrayM autorelease count 4
objc[27435]: [0x7fb86d8600b8] 0x7fb86d70fdb0 BBANewsFeedViewController autorelease count 2
objc[27435]: ##############
自动释放池只有一个页面时,打印结果如下:
(lldb) po _objc_autoreleasePoolPrint();
objc[30760]: ##############
objc[30760]: AUTORELEASE POOLS for thread 0x1000dedc0
objc[30760]: 3 releases pending.
objc[30760]: [0x101818000] ................ PAGE (hot) (cold) //只有一个页面,即是 hotPage 又是 coldPage
objc[30760]: [0x101818038] ################ POOL 0x101818038 // 自动释放池边界,nil
objc[30760]: [0x101818040] 0x100642e10 __NSCFString
objc[30760]: [0x101818048] 0x1010a2910 __NSArrayI
objc[30760]: ##############
OBJC_EXPORT void _objc_autoreleasePoolPrint(void)
void _objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
printAll
为 AutoreleasePoolPage
的静态方法,用于打印输出全部自动释放池信息
static void AutoreleasePoolPage::printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
for (page = coldPage(); page; page = page->child) {
page->print();
}
_objc_inform("##############");
}
打印单个 AutoreleasePoolPage
信息方法,调用实例方法 print()
void AutoreleasePoolPage::print()
{
_objc_inform("[%p] ................ PAGE %s %s %s", this,
full() ? "(full)" : "",
this == hotPage() ? "(hot)" : "",
this == coldPage() ? "(cold)" : "");
check(false);
for (id *p = begin(); p < next; p++) {
if (*p == POOL_BOUNDARY) {// 自动释放池边界,对象为 nil
_objc_inform("[%p] ################ POOL %p", p, p);
} else {
_objc_inform("[%p] %#16lx %s", // 待释放对象
p, (unsigned long)*p, object_getClassName(*p));
}
}
}