美文网首页
iOS 内存管理底层探究

iOS 内存管理底层探究

作者: yitez | 来源:发表于2020-06-07 09:27 被阅读0次

    内存管理方式:

    MRC:手动管理内存,需要开发人员管理内存,手动调用Release,以控制对象内存的释放。
    ARC:自动内存管理,系统控制内存的释放时机,主要由AutoReleasePool管理,释放时机有所延后,与RunLoop相关。

    引用计数:

    iOS对象的内存释放主要是引用计数决定,当一个对象初始化开始,每有一个指针指向该内存地址,引用计数就会增加,调用Retain增加引用计数,调用Release则减少引用计数,由哈希表管理每个对象的引用计数。

    sideTable:
    由sideTable管理,内部包含引用计数表和weak指针表。

    struct SideTable {
        spinlock_t slock;
      # mark 引用计数表     key:对象地址   value:引用计数
        RefcountMap refcnts;
       # mark 存放weak指针的表  key:对象地址   value:对应weak指针的数组
        weak_table_t weak_table;
    };
    

    PS:
    在底层代码里会看到很多isTaggedPointer和nonpointer的判断,这两者都是用于64位上的优化机制,大概了解一下即可。
    isTaggedPointer:对NSNumber,NSString之类常量的数据类型进行区别处理,将值直接保存在指针中,避免申请堆区的内存空间,所以也不需要维护引用计数。
    nonpointer:isa在64位上不再是一个单纯的指针,还会存储一些其他信息,nonpointer用来区别,两种情况区别处理。

    获取引用计数的方法:

    objc_object::rootRetainCount()
    {
    
    #mark 
        if (isTaggedPointer()) return (uintptr_t)this;
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
    #mark两种情况处理类似,只是nonpointer的话,部分数据可以直接在isa上取到
        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(); 
        #mark 单纯的指针类型 
        return sidetable_retainCount();
    }
    
    objc_object::sidetable_retainCount()
    {
       #mark 通过对象的地址获取对应的表
        SideTable& table = SideTables()[this];
        size_t refcnt_result = 1; 
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
        }
        table.unlock();
        return refcnt_result;
    }
    

    release方法:
    每次release调用都会减少引用计数,当引用计数小于临界值时的时候,会调用析构函数dealloc。

    - (oneway void)release {
        ((id)self)->rootRelease();
    }
    ALWAYS_INLINE bool 
    objc_object::rootRelease()
    {
        return rootRelease(true, false);
    }
    ALWAYS_INLINE bool 
    objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
    {
     #mark  rootRelease内部相关逻辑较多,包含指针类型判断和数据异常处理,节省篇幅,这里只有部分……
        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();
              #mark 加锁保护,真正处理的函数为sidetable_release。
                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:
           ……
    }
    
    

    sidetable_release方法的处理:
    1、根据对象获取对应的sideTable
    2、判断引用计数是否小于临界值
    2、引用计数小于临界值的,标记为需要释放,否则进行-1。
    4、如果标记为需要释放,则调用dealloc方法。

    
    objc_object::sidetable_release(bool performDealloc)
    {
    #if SUPPORT_NONPOINTER_ISA
        assert(!isa.nonpointer);
    #endif
    #mark  获取到SideTable
        SideTable& table = SideTables()[this];
        bool do_dealloc = false;
        table.lock();
        RefcountMap::iterator it = table.refcnts.find(this);
      #mark 表里找不到
        if (it == table.refcnts.end()) { 
            do_dealloc = true;
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
      #mark 引用计数小于临界值了,需要释放
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
     #mark 引用计数-1
            it->second -= SIDE_TABLE_RC_ONE;    
        }
        table.unlock();
      #mark 需要释放
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);//发送消息调用dealloc销毁对象。
        }
        return do_dealloc;
    }
    

    dealloc方法:
    对象自身的释放和对象相关变量和关联数据的处理。
    sideTable的处理,清除引用计数记录,释放weak指针。

    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    void _objc_rootDealloc(id obj)
    {
        assert(obj);
        obj->rootDealloc();
    }
    inline void objc_object::rootDealloc()
    {
        if (isTaggedPointer()) return;  // fixme necessary?
        #nark 没有其他关联的数据或者表需要处理,直接释放
        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);
        }
    }
    
    #mark object_dispose方法:
    
    id object_dispose(id obj) 
    {
        if (!obj) return nil;
    
        objc_destructInstance(obj);    
        free(obj);
    
        return nil;
    }
    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            // This order is important.
           #mark 释放对象的实例变量
            if (cxx) object_cxxDestruct(obj);
           #mark 移除动态关联的对象
            if (assoc) _object_remove_assocations(obj);
        
            obj->clearDeallocating();
        }
    
        return obj;
    }
    

    接下来是clearDeallocating方法:

    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());
    }
    

    sidetable_clearDeallocating和clearDeallocating_slow内部处理相似,通过weak表找到指向该对象的weak指针并释放,有引用计数记录的,则清除该记录。

    objc_object::sidetable_clearDeallocating()
    {
        SideTable *table = SideTable::tableForPointer(this);
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
    
        spinlock_lock(&table->slock);
        RefcountMap::iterator it = table->refcnts.find(this);
        if (it != table->refcnts.end()) {
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                #mark  如果有弱引用,还需要清理weak指针表
                weak_clear_no_lock(&table->weak_table, (id)this);
            }
            #mark 清理引用计数记录
            table->refcnts.erase(it);
        }
        spinlock_unlock(&table->slock);
    }
    

    释放weak指针
    获取weak的hash表,根据对象地址获取所有指向该对象的weak指针数组,将数组内元素置为nil。

    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        objc_object *referent = (objc_object *)referent_id;
    
        weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
        if (entry == nil) {
            /// XXX shouldn't happen, but does with mismatched CF/objc
            //printf("XXX no entry for clear deallocating %p\n", referent);
            return;
        }
    
        // zero out references
        weak_referrer_t *referrers;
        size_t count;
        
        if (entry->out_of_line()) {
            referrers = entry->referrers;
            count = TABLE_SIZE(entry);
        } 
        else {
            referrers = entry->inline_referrers;
            count = WEAK_INLINE_COUNT;
        }
        #mark 遍历数组,将指向对象的weak指针置为nil。
        for (size_t i = 0; i < count; ++i) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                if (*referrer == referent) {
                    *referrer = nil;
                }
                else if (*referrer) {
                    _objc_inform("__weak variable at %p holds %p instead of %p. "
                                 "This is probably incorrect use of "
                                 "objc_storeWeak() and objc_loadWeak(). "
                                 "Break on objc_weak_error to debug.\n", 
                                 referrer, (void*)*referrer, (void*)referent);
                    objc_weak_error();
                }
            }
        }
        
    

    weak指针:

    初始化weak指针的时候,会调用objc_initWeak函数。
    当weak指针指向一个对象时,会调用objc_storeWeak函数。
    当weak指针不再指向任何对象时,销毁会调用objc_destroyWeak函数。

    //objc_initWeak
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
    
    //objc_storeWeak:
    objc_storeWeak(id *location, id newObj)
    {
        return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object *)newObj);
    }
    
    //objc_destroyWeak
    objc_destroyWeak(id *location)
    {
        (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
            (location, nil);
    }
    

    可以看出,三个方法的最终走向都是storeWeak方法,storeWeak的内部实现:
    storeWeak内部逻辑主要是由 haveOld 和haveNew 判断,
    haveOld 代表weak指针是否已经指向其他老对象了,haveNew 代表需要指向新的对象了 。所以正常情况下是:
    objc_initWeak: haveOld = NO,haveNew = YES。
    objc_storeWeak: haveOld = YES,haveNew = YES。
    objc_destroyWeak:haveOld = YES,haveNew = NO。

    storeWeak(id *location, objc_object *newObj)
    {
        ASSERT(haveOld  ||  haveNew);
        if (!haveNew) ASSERT(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
        // Retry if the old value changes underneath us.
     retry:
       #mark有老对象则有oldTable 
        if (haveOld) {
            oldObj = *location;
            oldTable = &SideTables()[oldObj];  
        } else {
            oldTable = nil;
        }
      #mark有新对象则有newTable 
        if (haveNew) {
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil;
        }
    
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
        #mark 异常处理
        if (haveOld  &&  *location != oldObj) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            goto retry;
        }
    
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
         #mark 异常处理
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized()) 
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                class_initialize(cls, (id)newObj);
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls;
    
                goto retry;
            }
        }
    
        // Clean up old value, if any.
         #mark 从老对象的weak表里移除记录
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
        }
    
        // Assign new value, if any.
        #mark 绑定新对象的weak表
        if (haveNew) {
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // weak_register_no_lock returns nil if weak store should be rejected
    
            // Set is-weakly-referenced bit in refcount table.
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
    
            // Do not set *location anywhere else. That would introduce a race.
            *location = (id)newObj;
        }
        else {
            // No new value. The storage is not changed.
        }
        
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
        return (id)newObj;
    }
    

    自动释放池autoreleasepool:

    经常看到这样的代码:

        for (int i = 0 ; i< 555500; i++) {
            @autoreleasepool {
                TestARCObject *object = [[TestARCObject alloc]init];
                [object doSomeThing ];
            }
        }
    

    在创建大量的临时变量时,需要手动添加自动释放池,如果不添加自动释放池的时候,就会导致内存突然暴涨。因为ARC跟MRC不同,MRC是程序员可以手动释放每一个指针,而ARC下指针的释放是由autoreleasepool管理,然后批量进行release处理的。
    如果我们自己不添加autoreleasepool,那么将会由系统自动创建的autoreleasepool管理,就是这个 :

    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {   #mark 包裹在最外面的autoreleasepool
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    
    

    那么这个主线程的自动释放池什么时候对内部的指针进行release呢,虽然官方只是轻描淡写的一句“将会在合适的时候释放”,但是我们都能猜出其实是和RunLoop相关。
    通过控制台打印出主线程RunLoop相关信息:

    image.png
    搜索autoRelease相关数据,可以找到_wrapRunLoopWithAutoreleasePoolHandler方法:
    <CFRunLoopObserver 0x600001fcc640 [0x7fff8062ce40]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
    
    <CFRunLoopObserver 0x600001fcc6e0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
    
    

    可以看到activities有两个值,也就是说两种情况下会触发AutoreleasePool相关的回调方法,再查看RunLoop的状态枚举:

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0), 1
        kCFRunLoopBeforeTimers = (1UL << 1), 2
        kCFRunLoopBeforeSources = (1UL << 2),  4
        kCFRunLoopBeforeWaiting = (1UL << 5),     32
        kCFRunLoopAfterWaiting = (1UL << 6),        64
        kCFRunLoopExit = (1UL << 7),      128 ,
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    activities = 0x1(1)对应kCFRunLoopEntry,也就是进入runLoop状态,执行autoreleasepool相关初始化操作,
    activities = 0xa0(160 )对应kCFRunLoopExit | kCFRunLoopBeforeWaiting(32+128),也就是退出Runloop和即将进入休眠的状态,执行autoreleasepool相关释放操作。

    使用clang命令行转换后可以看到:

    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ {
            __AtAutoreleasePool __autoreleasepool;
            return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
        }
    }
    
    
    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    

    可以看到内部主要是构造函数push和析构函数pop方法,内部是由AutoreleasePoolPage管理。

    objc_autoreleasePoolPush(void)
    {
        if (UseGC) return NULL;
        return AutoreleasePoolPage::push();
    }
    
    objc_autoreleasePoolPop(void *ctxt)
    {
        if (UseGC) return;
        if (!ctxt) return;
        AutoreleasePoolPage::pop(ctxt);
    }
    

    AutoreleasePool自身没有具体的结构体数据,真正管理对象指针的是AutoreleasePoolPage,整体的数据结构是以AutoreleasePoolPage为单位的双向链表,AutoreleasePoolPage内部结构不细说了,除了保存一部分链表的信息数据外,其他剩余空间都是用来保存AutoreleasePool管理的对象指针。

     static inline void *push() 
        {
            if (!hotPage()) {  
    # hotPage代表当前的Page,如果没有会New一个,设为当前Page
                setHotPage(new AutoreleasePoolPage(NULL));
            } 
            id *dest = autoreleaseFast(POOL_SENTINEL);
            assert(*dest == POOL_SENTINEL);
            return dest;
        }
    
        static inline void pop(void *token) 
        {
            AutoreleasePoolPage *page;
            id *stop;
            if (token) {
               #mark 通过token获取对应的page
                page = pageForPointer(token);
                stop = (id *)token;
                assert(*stop == POOL_SENTINEL);
            } else {
                // Token 0 is top-level pool
                page = coldPage();
                assert(page);
                stop = page->begin();
            }
            #mark 释放Page内部指针直到碰到临界值
            page->releaseUntil(stop);
    
    …………(太多了,省略一下)
    
    

    这里的push是AutoReleasePool初始化第一次调用的时候,所以传入的autoreleaseFast的参数是POOL_SENTINEL,POOL_SENTINEL是边界对象,用于分隔的作用。
    Pop内部会获取相应的Page,然后调用releaseUntil方法,就开始批量释放内部指针了。

    相关文章

      网友评论

          本文标题:iOS 内存管理底层探究

          本文链接:https://www.haomeiwen.com/subject/iyeczhtx.html