面经

内存管理及相关面试题详解

2020-04-09  本文已影响0人  Dezi

一、内存管理

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. 将isabits中的extra_rc(引用计数)加1
5. 如果bitsextra_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底层分析

参考内存管理 — 自动释放池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 的时候。

上一篇 下一篇

猜你喜欢

热点阅读