iOS内存管理
内存分区
这里从低到高介绍一下iOS内存的分区及主要存储内容。
- 内核区
- 栈区:函数,方法,局部变量
- 堆区:通过alloc分配的对象
- 数据段
- 字符串常亮:如"abc"
- 未初始化数据:未初始化的全局变量量,静态变量量
- 已初始化数据:初始化的全局变量量,静态变量量
- 代码区:程序代码,加载到内存中
- 保留区
程序运行过程中数据主要是存在堆区和栈区的,而我们的内存管理管理的是堆上的内存,栈上的内存一般情况下不需要我们管理。
Tagged Point
Tagged Point是从64位开始才有的,目的是优化NSNumber、NSDate、NSString 等小对象的存储。
Tagged Pointer的指针不像其他对象指针指向一个地址,Tagged Pointer指针包含真正的值。所以,它的内存没有存在堆中,也不不需要malloc和free
优势是读取速度更快,更省空间。
引用计数MRC ARC
概念:引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。
iOS中采用了引用计数方案管理内存,分为MRC 手动引用计数 和 ARC 自动引用计数。 MRC时代需要程序员手动管理对象的生命周期,也就是对象的引用计数有程序员来控制,什么时候retain,什么时候release,完全自己掌握.ARC自动引用计数是编译器的一个特性,能够自动管理OC对象内存生命周期.在ARC中你需要专注于写你的代码, retain ,release, autorelease操作交给编译器去处理就行了.
ARC中禁⽌止⼿手动调⽤用retain/release/retainCount/dealloc。但是新加了weak、strong属性关键字
引用计数存储位置在哪里呢,我们先看下源码,源码下载地址 https://opensource.apple.com
// SUPPORT_NONPOINTER_ISA
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
我看这个版本号是objc4-787.1
,通过源码能够看出在retaincount方法中判断几点:1、isTaggedPointer,2、bits.nonpointer,3、bits.has_sidetable_rc。
首先isTaggedPointer这个前面已经说了,是对小对象类型的优化,指针就代表数据,没有isa指针;然后bits.nonpointer判断的是是否是优化过得isa指
针;bits.has_sidetable_rc判断是否使用sidetable存储引用计数。
这里要注意的是intptr_t rc = 1 + bits.extra_rc;
这行代码,bits.extra_rc和sidetable中存储的值比实际引用计数小1。也就是说并不是bits.extra_rc为0且sidetable中没有存储引用计数信息时,对象并不会被释放。而是在存储为0的时候再调用release才会释放。
下面依次来讲一下isa和sidetable。
isa
isa又是对指针的优化,这里放上一部分源码
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
这个只截取了arm64位定义的,后面的数字代码位数。加起来正好是64位,标识各个信息在isa中占用的位数。目的还是节省空间。下面简单介绍一下isa中包含的信息
- nonpointer 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址,1,代表优化过,使用位域存储更多的信息
- has_assoc 是否有设置过关联对象,在dealloc过程中会判断
- has_cxx_dtor 是否有C++的析构函数(.cxx_destruct)
- shiftcls 存储着Class、Meta-Class对象的内存地址信息
- magic 用于在调试时分辨对象是否未完成初始化
- weakly_referenced 是否有被弱引用指向过,如果没有,释放时会更快
- deallocating 对象是否正在释放
- has_sidetable_rc 引用计数器是否过大无法存储在isa中
- extra_rc 里面存储的值是引用计数器减1
sidetable
关于引用计数的存储,就是在isa. extra_rc 和 sidetable 中。还是先放一下sidetable定义的源码
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
里面有spinlock_t,这是个自旋锁,保证多线程安全的;RefcountMap就是引用计数表;weak_table_t 弱引用表。
retain,release
上面大概介绍了一下引用计数,isa,sidetable关系的概念,可以看出他们三者是息息相关的,那么具体的关系是什么么。可以看下retain,release相关源码
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
retain中主要做的事情是isa.extra_rc内保存引用计数+1,当isa.extra_rc上溢出时,将其一般引用计数放进sidetable中,并将has_sidetable_rc 置为 true。
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
release 中主要做的是将isa.extra_rc 减1,当isa.extra_rc下溢是进入underflow中,在underflow中判断has_sidetable_rc。如果为flase,调用dealloc去释放对象;如果为true,将sidetable中的引用计数挪出一部分给isa.extra_rc,然后重新回到retry。
自动释放池
AutoreleasePoolPage类内容太多,不全部贴出来了,在NSObject.mm文件中定义。调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的.
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
每个AutoreleasePoolPage对象占用4096字节内存,除了固定存储内部的成员变量外,其余空间用来存放autorelease对象的地址
调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
id *next指向了下一个能存放autorelease对象地址的区域
Runloop和Autorelease
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()