iOS 内存管理底层探究

内存管理方式:

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方法,就开始批量释放内部指针了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345