内存布局
- 栈区【一般为0x7开头】
- 函数
- 指针
- 局部变量、方法参数
- 堆区【一般为0x6开头】
- 通过alloc分配内存的对象
- block copy
- 未初始化数据 .bss【一般为0x1开头】
- 未初始化的全局变量
- 未初始化的静态变量
- 数据段 .data【一般为0x1开头】
- 初始化的全局变量
- 初始化的静态变量
- 代码段 .text
- 程序代码,加载到内存中
为什么栈中地址访问比堆要快?
因为栈中内存是通过sp寄存器去定位的,而堆区地址需要先通过寄存器定位到包含内存空间的地址,再通过这个地址定位到具体位置。
内存管理方案
TaggedPointer
基础
- 专门用来存储小对象(
NSNumber,NSDate...) - 指针的值不再是地址,而是真正的值。所以,实际上它只是一个披着对象皮的普通变量。所以,它的内存并不存储在堆中,也不需要
malloc和free - 在内存读取上有着3倍的效率,创建时比以前快106倍
NONPOINTER_ISA
基础
- 非指针型isa
- 我们在探究
isa_t的结构时,会看到其内部存储了一个ISA_BITFIELD,查看一下arm64架构下的ISA_BITFIELD
#define ISA_BITFIELD
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
nonpointer:是否对isa指针开启指针优化。【0:纯isa指针,1:不只是类对象地址,isa中包含了类信息、对象的引用计数】has_assoc:关联对象标志位。【0:没有,1:存在】has_cxx_dtor:该对象是否有C++/Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快地释放对象shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。magic:用于调试器判断当前对象是真的对象or没有初始化的空间weak_referenced:标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放has_sidetable_rc:当对象的引用计数大于10时,则需要借用该变量存储进位extra_rc:表示该对象的引用计数值,实际上是引用计数值减1.例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用到上面的has_sidetable_rc:当对象的引用计数大于10时,需要借用该变量存储进位
散列表SideTable
基础
散列表是有多张的,本质是StripedMap<SideTable>结构
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
···
}
根据StripedMap的定义可以知道,iphone上有8张SideTable,其它平台有64张SideTable
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 引用计数
weak_table_t weak_table; // 弱引用表
}
// weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
MRC & ARC
retain流程探索
当我们创建一个对象的时候,根据断点可以看到会走到_objc_rootRetain方法
// _objc_rootRetain
id _objc_rootRetain(id obj) {
ASSERT(obj);
return obj->rootRetain();
}
// rootRetain
id objc_object::rootRetain() {
return rootRetain(false, RRVariant::Fast);
}
// 也就是说最终实际调用的是重载的rootRetain方法,入参为false和Fast,下面我们探究该方法流程
id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) {···}
- 是否为
TaggedPointer,若是,直接返回自身(无引用计数),若不是,继续。
if (slowpath(isTaggedPointer())) return (id)this;
- 是否为
nonpointer类型,若为nonpointer- 判断是否正在析构,若在析构则不执行retain操作
extra_rc值+1,若carry则代表newisa.bits存满了导致溢出
// 位运算
uintptr_t carry; // carry代表是否溢出
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// RC_ONE
# define RC_ONE (1ULL<<56)
若溢出,且传入参数不为RRVariant::Full,走到rootRetain_overflow方法,并返回。
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// 可以看到该方法内部还是调用了rootRetain,但传入为RRVariant::Full
id objc_object::rootRetain_overflow(bool tryRetain) {
return rootRetain(tryRetain, RRVariant::Full);
}
若传入参数不为RRVariant::Full,将newisa.extra_rc只保留一半
// 将散列表的flag置为true
sideTableLocked = true;
transcribeToSideTable = true;
// extra_rc只保留一半,且将该对象的has_sidetable_rc置为true
newisa.extra_rc = RC_HALF; // # define RC_HALF (1ULL<<18)
newisa.has_sidetable_rc = true;
- 若不为
nonpointer类型,走到sidetable_retain
return sidetable_retain(sideTableLocked) // false
简单画了个流程图
weak
当我们用__weak修饰一个变量,断点运行可以看到来到了objc_initWeak方法。
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
下面探究storeWeak方法。
- 根据传入的
newObj获取散列表
newTable = &SideTables()[newObj];
2. 创建弱引用表
weak_register_no_lock(&newTable->weak_table,
(id)newObj,
location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
在weak_register_no_lock方法中重点关注以下几行
// 被弱引用对象地址
objc_object *referent = (objc_object *)referent_id;
// 被弱引用对象指针地址
objc_object **referrer = (objc_object **)referrer_id;
// 创建weak_entry_t
weak_entry_t new_entry(referent, referrer);
// 判断是否需要扩容
weak_grow_maybe(weak_table);
// 将新的weak_entry_t添加到弱引用表中
weak_entry_insert(weak_table, &new_entry);
weak_entry_t结构
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
··· // 由于是联合体位域的结构,我们暂时只关注下面这部分
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
// 初始化方法
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) { // WEAK_INLINE_COUNT为4
inline_referrers[i] = nil;
}
}
}
自动释放池
首先我们将一个空工程通过clang指令查看编译后的产物
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
// xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
__AtAutoreleasePool结构体中包含了一个构造函数和一个析构函数,分别对应objc_autoreleasePoolPush()函数和objc_autoreleasePoolPop()函数
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
来到objc源码中,可以看到这两个方法分别对应的是AutoreleasePoolPage的push()和pop(void *ctxt)方法。首先查看下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;
static size_t const COUNT = SIZE / sizeof(id);
static size_t const MAX_FAULTS = 2;
}
// AutoreleasePoolPageData
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
};
#endif
magic_t const magic; // 16
__unsafe_unretained id *next; // 8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; // 8
AutoreleasePoolPage *child; // 8
uint32_t const depth; // 4
uint32_t hiwat; // 4
}
magic:用来校验AutoreleasePoolPage的结构是否完整next:指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()thread:指向当前线程parent:指向父结点,第一个结点的parent为nilchild:指向子结点,最后一个结点的child为nildepth:代表深度,从0开始hiwat:代表high water mark,最大入栈数量标记