objc源碼解析|引用計(jì)數(shù)管理

通過以下方法查看iOS的引用計(jì)數(shù)管理:

  • alloc
  • retain
  • release
  • retainCount
  • dealloc

源碼版本:objc4-723

alloc


+ (id)alloc {
    return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

當(dāng)我們調(diào)用類的alloc方法時(shí)倦微,調(diào)用的方法棧如上所示,我們來看最后一部分代碼即可。

在這一部分代碼中,有兩個(gè)宏定義:

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect的主要作用就是幫助編譯器判斷條件跳轉(zhuǎn)的預(yù)期值,避免因執(zhí)行jmp跳轉(zhuǎn)指令造成時(shí)間浪費(fèi)。

編譯器優(yōu)化時(shí)荐虐,根據(jù)條件跳轉(zhuǎn)的預(yù)期值,按正確地順序生成匯編代碼丸凭,把“很有可能發(fā)生”的條件分支放在順序執(zhí)行指令段福扬,而不是jmp指令段(jmp指令會(huì)打亂CPU的指令執(zhí)行順序腕铸,大大影響CPU指令執(zhí)行效率)。
在本例中铛碑,if else句型編譯后, 一個(gè)分支的匯編代碼緊隨前面的代碼狠裹,而另一個(gè)分支的匯編代碼需要使用jmp指令才能訪問到,很明顯通過jmp訪問需要更多的時(shí)間, 在復(fù)雜的程序中汽烦,有很多的if else句型,又或者是一個(gè)有if else句型的庫(kù)函數(shù)涛菠,每秒鐘被調(diào)用幾萬次,通常程序員在分支預(yù)測(cè)方面做得很糟糕撇吞,編譯器又不能精準(zhǔn)的預(yù)測(cè)每一個(gè)分支俗冻,這時(shí)jmp產(chǎn)生的時(shí)間浪費(fèi)就會(huì)很大,函數(shù)__builtin_expert()就是用來解決這個(gè)問題的梢夯。

所以言疗,這里的邏輯就是如果slowpath(checkNil && !cls)0時(shí),該函數(shù)返回nil颂砸,為1時(shí)走接下里的邏輯。

在接下來的邏輯中死姚,fastpath(!cls->ISA()->hasCustomAWZ()首先會(huì)判斷當(dāng)前classsuper class是否實(shí)現(xiàn)了allocWithZone:方法(AWZAllocWithZone)人乓,如果沒有實(shí)現(xiàn)這個(gè)方法,最終都會(huì)調(diào)用C語(yǔ)言函數(shù)calloc申請(qǐng)一塊內(nèi)存空間都毒。

如果實(shí)現(xiàn)了AWZ方法色罚,就會(huì)執(zhí)行allocWithZone:

總結(jié)账劲,調(diào)用alloc時(shí)戳护,經(jīng)過一系列方法調(diào)用,最終會(huì)在內(nèi)存中申請(qǐng)一塊內(nèi)存空間瀑焦,但此時(shí)并沒有設(shè)置引用計(jì)數(shù)腌且。

retain


- (id)retain {
    return ((id)self)->rootRetain();
}


ALWAYS_INLINE id 
objc_object::rootRetain()
{
    return rootRetain(false, false);
}

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

在上面代碼塊中,因?yàn)槠奘∪チ?code>rootRetain()的具體實(shí)現(xiàn)代碼榛瓮,在rootRetain()中實(shí)際上調(diào)用了sidetable_retain()方法铺董,也就是最后那部分代碼。

在了解最后一部分代碼前禀晓,需要了解一些概念上的知識(shí)精续,有助于更好的理解系統(tǒng)對(duì)對(duì)象引用計(jì)數(shù)管理的實(shí)現(xiàn)原理。

為了管理所有對(duì)象的引用計(jì)數(shù)以及對(duì)象的所有弱引用指針粹懒,系統(tǒng)創(chuàng)建了一個(gè)全局的SideTables重付,SideTables里存的是一個(gè)個(gè)的SideTable,每個(gè)SideTable實(shí)際上都是一個(gè)結(jié)構(gòu)體凫乖,如下:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    //省略部分代碼

SideTables本質(zhì)上是一個(gè)全局的hash表确垫,key值為對(duì)象的內(nèi)存地址愕把。
回到sidetable_retain()方法,首先會(huì)根據(jù)當(dāng)前對(duì)象的指針到SideTables中獲取SideTable

SideTable& table = SideTables()[this];

然后在SideTable的結(jié)構(gòu)體當(dāng)中獲取當(dāng)前對(duì)象的引用計(jì)數(shù)值:

size_t &refcntStorage = table.refcnts[this];

需要注意的是森爽,這兩次查找都是hash查找恨豁。
然后再對(duì)應(yīng)用計(jì)數(shù)值進(jìn)行+1操作:

refcntStorage += SIDE_TABLE_RC_ONE;

以上,就是進(jìn)行retain操作時(shí)爬迟,系統(tǒng)對(duì)對(duì)象引用計(jì)數(shù)操作的具體實(shí)現(xiàn)橘蜜。

release


- (oneway void)release {
    ((id)self)->rootRelease();
}

ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } 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)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

篇幅所限,省略了rootRelease()的代碼實(shí)現(xiàn)付呕,在rootRelease()中會(huì)調(diào)用sidetable_release()计福,也就是最后一部分代碼,我們可以將最后一部分代碼進(jìn)行簡(jiǎn)化徽职,簡(jiǎn)化后的代碼如下所示:

SideTable &table = SideTables()[this];
RefcountMap ::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;

首先根據(jù)對(duì)象的內(nèi)存地址到hash表中查找SideTable

SideTable &table = SideTables()[this];

然后根據(jù)查找到的SideTable和對(duì)象的內(nèi)存地址獲取其引用計(jì)數(shù)并-1

RefcountMap ::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;

另外象颖,當(dāng)對(duì)象需要被回收時(shí),系統(tǒng)還會(huì)利用objc_msgSend向?qū)ο蟀l(fā)送dealloc消息姆钉。

以上说订,就是進(jìn)行release操作時(shí),系統(tǒng)對(duì)對(duì)象引用計(jì)數(shù)操作的具體實(shí)現(xiàn)潮瓶。

retainCount


- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}


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

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

這里把retainCount方法調(diào)用棧的全部代碼都貼出來了陶冷,主要來看最后一部分代碼,也就是sidetable_retainCount()方法的代碼毯辅。

我們也可以帶著一個(gè)問題來看這段代碼埂伦,問題是:

為什么剛創(chuàng)建完的對(duì)象,它的retainCount0思恐?

在這個(gè)方法的實(shí)現(xiàn)中沾谜,首先會(huì)根據(jù)對(duì)象的內(nèi)存地址獲取到SideTable

SideTable& table = SideTables()[this];

然后緊接著聲明了一個(gè)局部變量refcnt_result,值為1

size_t refcnt_result = 1;

所以胀莹,剛alloc出來的對(duì)象基跑,在引用計(jì)數(shù)表(SideTables)中是沒有這個(gè)對(duì)象的key-value映射的,所以table.refcnts.find(this)讀出來的值是0嗜逻,所以如果這里是0涩僻,就將局部變量refcnt_result的值返回,也即是1栈顷。

如果table.refcnts.find(this)讀出來的值不是0逆日,則會(huì)把查找的結(jié)果做向右偏移的操作然后+1,返回給調(diào)用方:

refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;

以上萄凤,就是進(jìn)行retainCount操作時(shí)室抽,系統(tǒng)對(duì)獲取對(duì)象引用計(jì)數(shù)的具體實(shí)現(xiàn)原理。

dealloc


- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

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

以上是dealloc的方法調(diào)用棧靡努,從最后一個(gè)方法開始看坪圾,在這個(gè)方法中有一個(gè)if判斷條件比較多:

  • 首先判斷有沒有使用非指針型isanonpointer_isa
  • 判斷是否有weak指針指向它
  • 判斷是否有關(guān)聯(lián)對(duì)象
  • 判斷內(nèi)部實(shí)現(xiàn)是否涉及到有C++相關(guān)的內(nèi)容
  • 當(dāng)前對(duì)象的引用計(jì)數(shù)是否是通過SideTable來維護(hù)的

只有當(dāng)當(dāng)前對(duì)象既不是nonpointer_isa晓折,也沒有弱引用,還沒有涉及到C++兽泄、關(guān)聯(lián)對(duì)象漓概、沒有使用SideTable存儲(chǔ)相關(guān)引用計(jì)數(shù),才會(huì)調(diào)用C函數(shù)free()進(jìn)行釋放病梢,否則胃珍,就調(diào)用object_dispose()對(duì)象清除函數(shù)進(jìn)行清除。

再來看object_dispose()函數(shù)的實(shí)現(xiàn):

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.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

先來看objc_destructInstance函數(shù)蜓陌,如果obj對(duì)象存在的話觅彰,會(huì)先判斷當(dāng)前對(duì)象是否有用到涉及到C++相關(guān)的東西,如果有钮热,稍后會(huì)調(diào)用object_cxxDestruct函數(shù)進(jìn)行銷毀操作填抬。

還會(huì)判斷當(dāng)前對(duì)象是否擁有關(guān)聯(lián)對(duì)象,如果有的話稍后就會(huì)調(diào)用_object_remove_assocations函數(shù)進(jìn)行清除操作隧期,所以我們并不需要在dealloc方法中顯式的清除關(guān)聯(lián)對(duì)象飒责。

最后會(huì)調(diào)用objclearDeallocating函數(shù),函數(shù)實(shí)現(xiàn)如下:

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

其會(huì)調(diào)用兩個(gè)函數(shù)厌秒,分別是:

  • sidetable_clearDeallocating()
  • clearDeallocating_slow()

(1)sidetable_clearDeallocating()函數(shù)實(shí)現(xiàn)如下:

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

其中還會(huì)調(diào)用weak_clear_no_lock()函數(shù)读拆,實(shí)現(xiàn)如下:

void 
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;
    }
    
    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_entry_remove(weak_table, entry);
}

weak_clear_no_lock函數(shù)有兩個(gè)參數(shù),weak_tablereferent_id鸵闪。

weak_table就是弱引用表,記錄了指向該對(duì)象的弱引用指針暑诸。

referent_id就是當(dāng)前正在被銷毀的對(duì)象蚌讼。

這里會(huì)把referent_id強(qiáng)轉(zhuǎn)成objc_object類型的結(jié)構(gòu)體指針,記做referent个榕,根據(jù)referentweak_table中獲取entry篡石,進(jìn)而通過weak_entry_t獲取弱引用數(shù)組referrers,繞后遍歷這個(gè)referrers數(shù)組西采,將指向該對(duì)象的弱引用指針全部置為nil凰萨,最后從weak_table中把這個(gè)entry刪除。

所以械馆,當(dāng)一個(gè)對(duì)象有一個(gè)弱引用指針指向它的時(shí)候胖眷,當(dāng)這個(gè)對(duì)象被廢棄之后它的弱引用指針會(huì)被自動(dòng)置為nil

(2)clearDeallocating_slow()函數(shù)實(shí)現(xiàn)如下:

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

table.refcnts.erase(this);函數(shù)的主要作用是從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)霹崎。

總結(jié)一下珊搀,當(dāng)對(duì)象被釋放時(shí),也就是在系統(tǒng)的dealloc方法實(shí)現(xiàn)中尾菇,系統(tǒng)會(huì)銷毀該對(duì)象所使用的C++相關(guān)的內(nèi)容境析,還會(huì)刪除該對(duì)象的關(guān)聯(lián)對(duì)象囚枪,并且將指向該對(duì)象的弱引用指針全部置為nil,這些操作完成后劳淆,會(huì)從全局的引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)链沼。

以上,就是dealloc的實(shí)現(xiàn)原理沛鸵。

內(nèi)存管理方案


分類

  • TaggedPointer
    針對(duì)于小對(duì)象括勺,如NSNumber、NSDate等谒臼。
  • NONPOINTER_ISA
    對(duì)于64位架構(gòu)下的應(yīng)用程序采用這種內(nèi)存管理方案朝刊,在64位架構(gòu)下,ISA指針占64個(gè)bit位蜈缤,實(shí)際上有32位或40位就夠用了拾氓,剩余的是浪費(fèi)的,Apple為了提高內(nèi)存的利用率底哥,在剩余的bit位當(dāng)中存儲(chǔ)一些關(guān)于內(nèi)存管理的數(shù)據(jù)內(nèi)容咙鞍,也被稱為非指針型的isa。
  • 散列表
    包括引用計(jì)數(shù)表和弱引用表趾徽。

NONPOINTER_ISA

arm64架構(gòu)
arm64架構(gòu)
  • indexed
    0:代表它是一個(gè)純的isa指針续滋,里面的內(nèi)容直接代表當(dāng)前對(duì)象的類對(duì)象的地址。
    1:代表isa指針里面存儲(chǔ)的不僅是類對(duì)象的地址孵奶,還有一些內(nèi)存管理的數(shù)據(jù)疲酌。
  • has_assoc
    當(dāng)前對(duì)象是否有關(guān)聯(lián)對(duì)象
    0:沒有
    1:有
  • has_cxx_dtor
    當(dāng)前對(duì)象是否有使用到C++語(yǔ)言方面的內(nèi)容
  • 后續(xù)33位
    當(dāng)前對(duì)象的類對(duì)象的指針地址
  • magic
    略,不影響分析
  • weakly_referenced
    當(dāng)前對(duì)象是否有弱引用指針
  • deallocating
    表示當(dāng)前對(duì)象是否在進(jìn)行dealloc操作
  • has_sidetable_rc
    指當(dāng)前isa指針當(dāng)中如果所存儲(chǔ)的引用計(jì)數(shù)已經(jīng)達(dá)到了上限的話了袁,需要外掛一個(gè)sidetable這樣的數(shù)據(jù)結(jié)構(gòu)去存儲(chǔ)相關(guān)的引用計(jì)數(shù)內(nèi)容朗恳。
  • extra_rc
    額外的引用計(jì)數(shù),當(dāng)引用計(jì)數(shù)在一個(gè)很小的值得范圍內(nèi)就會(huì)存到isa指針當(dāng)中载绿,而不是由單獨(dú)的引用計(jì)數(shù)表去存儲(chǔ)引用計(jì)數(shù)粥诫,

散列表方式

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

在非嵌入式系統(tǒng)中,SideTables管理著64個(gè)SideTable崭庸,每個(gè)SideTable有3個(gè)元素:

  • spinlock_t自旋鎖
  • RefcountMap引用計(jì)數(shù)表
  • weak_table_t弱引用表

為什么不是一個(gè)SideTable怀浆,而是由多個(gè)SideTable組成SideTables

如果使用一個(gè)SideTable管理系統(tǒng)所有對(duì)象的引用計(jì)數(shù)怕享,那么當(dāng)我們?cè)诙嗑€程中對(duì)其中一個(gè)對(duì)象進(jìn)行retain执赡、release等操作時(shí)就會(huì)對(duì)這個(gè)SideTable加鎖,以此保證數(shù)據(jù)訪問的安全熬粗,那么在這個(gè)過程中實(shí)際上就產(chǎn)生了效率的問題搀玖,下一個(gè)對(duì)象要想進(jìn)行操作(比如修改引用計(jì)數(shù)),就必須要等鎖釋放只會(huì)才能操作這張表驻呐,如果成千上萬個(gè)對(duì)象同操作一張表灌诅,那么效率是極其低下的芳来。

系統(tǒng)為了解決效率低下的問題,系統(tǒng)引入了分離鎖的技術(shù)方案猜拾,我們可以把對(duì)象所對(duì)應(yīng)的引用計(jì)數(shù)表可以分拆成多個(gè)部分即舌,比如可以分拆成8個(gè),那么就會(huì)對(duì)8個(gè)表分別加鎖挎袜,假如A對(duì)象屬于表1顽聂,B對(duì)象屬于表2,那么此時(shí)就可以進(jìn)行并行操作盯仪,提高了訪問效率紊搪。

如何實(shí)現(xiàn)快速分流?

指通過一個(gè)對(duì)象的指針全景,如何快速定位到它屬于哪張SideTable耀石?

SideTable的本質(zhì)是一張Hash表,這張Hash表中可能有64張具體的SideTable用以存儲(chǔ)不同對(duì)象的引用計(jì)數(shù)表和弱引用表爸黄。

Hash表的key是對(duì)象指針經(jīng)過hash函數(shù)計(jì)算出一個(gè)值滞伟,這個(gè)值決定對(duì)象所屬的SideTable是哪一個(gè),或者說在數(shù)組中(SideTables)的位置是第幾個(gè)炕贵。

Hash查找

eg:給定值是對(duì)象的內(nèi)存地址梆奈,目標(biāo)值是數(shù)組(SideTables)下標(biāo)索引。

f(ptr) = (uintptr_t)ptr % array.count

其他涉及到的技術(shù)


  • spinlock_t slock;自旋鎖
  • RefcountMap refcnts;引用計(jì)數(shù)表
  • weak_table_t weak_table;弱引用表

spinlock_t

  • 自旋鎖是一種忙等的鎖称开,指當(dāng)前鎖已經(jīng)被其他線程獲取亩钟,當(dāng)前線程會(huì)不斷探測(cè)這個(gè)鎖是否有被釋放,如果被釋放掉自己會(huì)第一時(shí)間獲取這個(gè)鎖鳖轰。
    • 信號(hào)量是如果獲取不到這個(gè)鎖的時(shí)候径荔,會(huì)把自己線程進(jìn)行阻塞休眠,等到其他線程釋放這個(gè)鎖的時(shí)候喚醒當(dāng)前線程脆霎。
  • 適用于輕量訪問,因?yàn)閷?duì)于引用計(jì)數(shù)的操作只是簡(jiǎn)單的+1 -1操作狈惫,這種操作都是輕量的操作睛蛛,所以在輕量的訪問場(chǎng)景下,都可以使用自旋鎖胧谈。

RefcountMap

實(shí)際上是一個(gè)Hash表忆肾,可以通過對(duì)象的指針查找到對(duì)應(yīng)的引用計(jì)數(shù),查找的過程也是哈希查找菱肖,提高查找效率客冈。

  • size_t共有64位
    • 第一位是weakly_referenced,表示對(duì)象是否有弱引用稳强,0就是沒有场仲,1就是有和悦。
    • 第二位是deallocating,表示當(dāng)前對(duì)象是否正在dealloc渠缕。
    • 其余位存儲(chǔ)的就是對(duì)象的實(shí)際引用計(jì)數(shù)值鸽素,具體的引用計(jì)數(shù)值實(shí)際上要偏移兩位,因?yàn)榍懊嬗袃晌皇?code>weakly_referenced和deallocating亦鳞,我們要把這兩位去掉馍忽。

weak_table_t

實(shí)際上也是一張Hash表,key為對(duì)象指針燕差,valueweak_entry_t遭笋,weak_entry_t是一個(gè)結(jié)構(gòu)體數(shù)組,數(shù)組存儲(chǔ)的每一個(gè)對(duì)象就是弱引用指針徒探,也就是在代碼中定義的__weak變量瓦呼,這個(gè)變量的指針就存儲(chǔ)在weak_entry_t中。

完刹帕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吵血,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子偷溺,更是在濱河造成了極大的恐慌蹋辅,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挫掏,死亡現(xiàn)場(chǎng)離奇詭異侦另,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尉共,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門褒傅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袄友,你說我怎么就攤上這事殿托。” “怎么了剧蚣?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵支竹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鸠按,道長(zhǎng)礼搁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任目尖,我火速辦了婚禮馒吴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己饮戳,他們只是感情好豪治,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莹捡,像睡著了一般鬼吵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篮赢,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天齿椅,我揣著相機(jī)與錄音,去河邊找鬼启泣。 笑死涣脚,一個(gè)胖子當(dāng)著我的面吹牛叫确,可吹牛的內(nèi)容都是我干的影斑。 我是一名探鬼主播溯捆,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锨匆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了匪傍?” 一聲冷哼從身側(cè)響起暖璧,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤音半,失蹤者是張志新(化名)和其女友劉穎弄喘,沒想到半個(gè)月后玖喘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘑志,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年累奈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片急但。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澎媒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出波桩,到底是詐尸還是另有隱情戒努,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布镐躲,位于F島的核電站柏卤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏匀油。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一勾笆、第九天 我趴在偏房一處隱蔽的房頂上張望敌蚜。 院中可真熱鬧,春花似錦窝爪、人聲如沸弛车。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纷跛。三九已至喻括,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贫奠,已是汗流浹背唬血。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唤崭,地道東北人拷恨。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谢肾,于是被迫代替她去往敵國(guó)和親腕侄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容