- 父类和子类也是双向链表结构
- 自动释放器绑定在线程上
- 一个自动释放池 一个哨兵对象
llvm中以alloc copy new mutablecopy命名的不会加入到自动释放池中,不受自动释放池的影响
初探
我们建一个工程,main.m,通过xcrun或者clang编译为main.cpp文件 ```objectivec
import
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here... 开始压栈 ----------------------------
NSLog(@"Hello, World!");
} 作用域结束,开始出栈-------------------
return 0;
}
- 在编译后的.cpp文件中,该函数被编译为了如下代码
- @autoreleasepool编译完,首先做了注释,然后多了__AtAutoreleasePool __autoreleasepool
- 接着在.cpp查看__AtAutoreleasePool,是结构体,包含了构造函数和析构函数
- **__AtAutoreleasePool __autoreleasepool 调用等于构造函数,出了作用域后调用其析构函数**
- 因而在@autoreleasepool 作用域内,将对象进行压栈操作,操作作用域进行自动释放池的析构操作
```objectivec
#pragma clang assume_nonnull end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_xk0q0gh50qsgfk5m4r5ynbb80000gn_T_main_9ae6df_mi_0);
}
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
- 断点调试,通过汇编查看,其调用了libobjc库的objc_autoreleasePoolPush和objc_autoreleasepoolPop
结构
libobjc库中,搜寻objc_autoreleasePoolPush,其调用了AutoreleasePoolPage::push()
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
AutoreleasePoolPageData
AutoreleasePoolPage继承于AutoreleasePoolPageData
- AutoreleasePoolPageData是一个结构体,其中定义了最大链表数量16,存储的对象指针最多个数2^16 - 1, 另包含了以下成员变量
- magic_t const magic (16字节:其内部static 静态变量不占内存) -> 用来校验AutoreleasePoolPage的完整性
- __unsafe_unretained id *next (8字节) -> 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向
- AutoreleasePoolPageData是一个结构体,其中定义了最大链表数量16,存储的对象指针最多个数2^16 - 1, 另包含了以下成员变量
begin()
- pthread_t const thread (8字节) -> 指向当前线程
- AutoreleasePoolPage * const parent (8字节)-> 指向父结点,第一个结点的 parent 值为 nil
- AutoreleasePoolPage *child (8字节)-> 指向子结点,最后一个结点的 child 值为 nil
- uint32_t const depth (4字节) -> 代表深度,从 0 开始,往后递增 1
- uint32_t hiwat (4字节)-> 代表 high water mark 最大入栈数量标记
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
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(__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)
{
}
};
AutoreleasePoolPage
- AutoreleasePoolPage的结构
- 一个线程的自动释放池是一个栈帧结构,自动释放池绑定在线程上
- 自动释放池管理的是对象,哨兵是自动释放池的边界
- 聚焦(正在操作)的页面是hot pool,其它页面称为code 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();
}
....
}
begin
AutoreleasePoolPage调用的begin()函数,断点调试,大小为56,即上述AutoreleasePoolPageData结构体成员变量所占内存之和
- 自动释放池分页原因
- 页面操作是一个不断压栈、出栈的流程,对内存操作非常频繁,如果只有一个页面。所有对象都在一起,加锁、解锁效率低下
- 管理方便,当只有一个页面,某一处发生问题,那么全部页面将出现错误,分页则保证了稳定性
- 操作系统也是有内存分页,当只有一页,占用内存空间大,需要很长的连续的一段内存,分页后可以不连续,利用率、效率高
- 新插入objc应该从56位置开始,每个页面都包含成员变量,具体流程为
- 先插入哨兵对象,再插入需要释放的objc指针
- 当页面满了时,开辟新的页面,同时设置hot pool
objc_thread_self
获取当前线程,为tls,线程局部存储
__attribute__((const))
static inline pthread_t objc_thread_self()
{
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
初始化NSObject,然后在buildSettings关闭引用计数Objective-C Automatic Reference Counting, objc调用autorelease方法,extern _objc_autoreleasePoolPrint方法
- 2 releases pending。包含了两个对象,哨兵对象(0x10e80d038) + objc (0x108c04110)
- @autoRelease和autorelease区别
- 前者是一个自动释放池,后者是一个动作,压栈操作,将对象压入自动释放池
- 循环调用objc的autorelease进行压栈,加入505个时,出现了内存分页
- 所以一个内存页面的大小为 504 8 + 56 + 8(哨兵)= 4096,即4 1024 = 4k
- 此过程中哨兵对象并未因新开内存页而创建,所以一个自动释放池中只有一个哨兵对象
结构图
Q
- 临时变量什么时候释放?
- 作用域结束时释放
- 自动释放池的原理?
- 双向链表,哨兵,分页,内部成员变量等
自动释放池是否可以嵌套?
Runloop启动时创建AutoReleasePool,Runloop结束时销毁AutoReleasePool
- 当Runloop即将进入休眠时,代表一次事件处理完成,此时销毁AutoReleasePool
- 程序启动、唤醒 -> 用户点击屏幕 -> Cocoa Touch生成一个触摸事件(Event Object) -> Cocoa Touch生成一个AutoReleasePool -> Application处理Event Object -> 将Application Objects加入到AutoReleasePool中