内存管理及相关面试题详解
一、内存管理
1. 内存五大区
内存布局栈区(Stack):速度比较快,寄存器直接访问内存空间。编译器自动分配,由系统管理,在不需要的时候自动清除。主要存函数,方法,实参,局部变量,常量类型。内存地址:一般为0x7开头。
堆区(heap):速度比较慢,通过栈区指针找到变量再找到指向的内存空间。通过new,alloc、block copy创建的对象存储在这里,是由开发者管理的,需要告诉系统什么时候释放内存。ARC
下编译器会自动在合适的时候释放内存,而在MRC
下需要开发者手动释放。内存地址:一般为0x6开头。
未初始化数据、全局区(.bss):未初始化的全局变量,静态变量,程序运行过程中内存中的数据一直存在,程序结束后由系统释放。内存地址:一般为0x1开头。
已初始化数据、常量区(.data):初始化的全局变量,静态变量,程序结束后由系统释放。内存地址:一般为0x1开头。
代码段、代码区(.text):存放程序运行时的代码,代码会被编译成二进制存进内存的代码区。
二、MRC 手动管理引用计数,alloc,retian,release,retainCount,autorelease,dealloc
1. alloc底层分析
alloc
创建对象并申请一块不少于16字节的内存空间,此时引用计数为0,但是retainCount的时候引用计数为1了。关于alloc的实现请参考OC对象底层探索 — alloc创建对象。
2. retainCount底层分析
int main(int argc, const char * argv[]) {
@autoreleasepool { // 进作用域空间push,出作用域空间pop
// insert code here...
NSLog(@"Hello, World!");
NSObject *objc = [NSObject alloc]; // 0
NSLog(@"----- %ld",objc.retainCount); // ----- 1
NSLog(@"===== %ld",objc.retainCount); // ===== 1
}
return 0;
}
查看底层源码分析:
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; // extra_rc=0,存在isa中的引用计数是0
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock(); // 散列表
}
sidetable_unlock(); // 解锁
return rc; // 1
}
sidetable_unlock();
return sidetable_retainCount();
}
通过追踪源码,会看到对象
alloc
时,引用计数实际为0,但是系统默认 +1,所以alloc
创建对象获取到对象的引用计数实际为1
3. retain底层分析
我们找到objc_retain
方法,首先会判断isTaggedPointer
,不是就调用retain
方法。
id objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
retain
方法内部调用rootRetain
方法。fastpath
:大概率发生。
id objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
rootRetain
方法是retain
的核心方法,此处可以看出引用计数的变化。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
// retain 引用计数处理
do {
transcribeToSideTable = false;
//读取操作原子化,根据 CPU 不同实现不同,比如在 x86-64 上就是单纯的直接返回值,而在 arm64 上则使用了 ldxr 指令,获取isa.bits 的值
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;
}
rootRetain总结:
1. 判断是否是TaggedPointer,如果是直接返回
2. 判断isa是否经过NONPOINTER_ISA优化,没有则将引用计数存储在sidetable
(散列表)中
3. 检查对象是否正在析构
4. 将isa
的bits
中的extra_rc
(引用计数)加1
5. 如果bits
的extra_rc
已经存满了,则将其中的一半存储到sidetable
中
4. release底层分析
释放对象方法时用到了release
,实际上就是引用计数 -1。
先进入objc_release
中,判断是不是TaggedPointer
,不是则调用release
方法。
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
release
内部主要调用了rootRelease
方法:
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}
rootRelease
是release引用计数 -1的核心代码:
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;
}
rootRelease总结:
1. 使用LoadExclusive
获取isa
,将isa
中的引用计数 -1,调用StoreReleaseExclusive
方法保存新的isa
2. 因为还使用了sidetable
散列表保存引用计数,所以得取出来newisa.extra_rc = borrowed - 1
做引用计数 -1 操作更新isa
3. 如果sidetable
中也没有获取到引用计数的话,就说明没有变量引用了当前对象(retainCount = 0)
4. 没有变量引用当前对象,最后会调用objc_msgSend
向当前对象发送dealloc
消息
5. @autoreleasepool底层分析
6. dealloc底层分析
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
- isa.nonpointer非指针类型
- !isa.weakly_referenced对象没有被weak表引用
- !isa.has_assoc没有关联对象
- !isa.has_cxx_dtor没有自定义的C++析构方法
- !isa.has_sidetable_rc没有用散列表存引用计数
- free快速释放
- else:
object_dispose
方法内部主要调用了objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(); // cxx
bool assoc = obj->hasAssociatedObjects(); // 关联对象
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
- object_cxxDestruct 走C++析构函数逻辑
- _object_remove_assocations 移除当前关联对象
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating(); // 散列表清空
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
- 如果有isa.weakly_referenced 弱引用计数、isa.has_sidetable_rc 散列表计数
- 调用clearDeallocating_slow
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
// 清理弱引用计数表
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
// 清理引用表里的引用计数
table.refcnts.erase(this);
}
table.unlock();
}
以上即对dealloc销毁对象的完整分析。
dealloc调用时机:引用计数为0 的时候。