iOS探究 --- 內(nèi)存管理

內(nèi)存布局

代碼段:保存程序二進(jìn)制。
bss:一般保存全局靜態(tài)變量等倘要。
data:保存初始化的全局變量圾亏,靜態(tài)變量。
棧:保存函數(shù)封拧,方法志鹃。iOS開發(fā)中一般為0x7段。
堆:通過alloc出來的對象泽西,保存在堆里曹铃。 0x6段

image.png

TaggedPointer

TaggedPointer為蘋果為內(nèi)存優(yōu)化提供的一個方案。
主要存儲一些小字節(jié)的數(shù)據(jù)捧杉。


image.png

image.png

TaggesPointer被設(shè)計為2大部分陕见,標(biāo)志位區(qū)和數(shù)據(jù)區(qū)秘血。
由特殊標(biāo)志位來標(biāo)志類型。使得讀寫更加效率评甜。

SideTables

哈希表

先說說存儲結(jié)構(gòu)灰粮,實際上在我們學(xué)過的數(shù)據(jù)結(jié)構(gòu)可以歸結(jié)為兩類:連續(xù)的的存儲結(jié)構(gòu)和不聯(lián)系的存儲結(jié)構(gòu),其代表分別為數(shù)組和鏈表忍坷。而我們學(xué)過的堆棧粘舟,隊列,樹承匣,圖蓖乘,都可以用這兩種結(jié)構(gòu)來實現(xiàn)。連續(xù)的存儲結(jié)構(gòu)——數(shù)組韧骗,在數(shù)據(jù)的查找和修改上具有很好的優(yōu)點嘉抒,很方便,時間復(fù)雜度很小袍暴。但是在數(shù)據(jù)的增添和刪除上則顯得很麻煩些侍,空間復(fù)雜度很大。而非連續(xù)政模,非順序的存儲結(jié)構(gòu)——鏈表恰和數(shù)組相反岗宣,數(shù)據(jù)的增添和刪除容易,空間復(fù)雜度很小淋样,查找和修改復(fù)雜耗式,時間復(fù)雜度很大。

那么有沒有一種數(shù)據(jù)結(jié)構(gòu)能折衷一下數(shù)組和鏈表的優(yōu)缺點呢趁猴?那就是——哈希表刊咳,既滿足了數(shù)據(jù)的查找和修改很容易,同時又不占用很多空間的特點儡司。

哈希表是基于哈希函數(shù)的娱挨,哈希表中的元素是有哈希函數(shù)確定的,哈希表作為一種數(shù)據(jù)結(jié)構(gòu)捕犬,我們用哈希表來存儲數(shù)據(jù)跷坝,在保存的時候存入的是一個<key—value>的結(jié)構(gòu),value由哈希函數(shù)作用于key上得到碉碉。但是存在一個哈希沖突問題柴钻,那就是當(dāng)你用hash函數(shù)作用在兩個互不相同的key上,得到的value值相等垢粮。

在runtime中顿颅,維護(hù)著一個sideTables,他是一個哈希表。


image.png

sideTables由多個SideTable組成粱腻。


image.png

sideTable又由一個自旋鎖slock,一個引用計數(shù)表refcnts斩跌,一個弱引用表weak_table組成绍些。

Retain和Release

  1. Retain
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            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;
}

當(dāng)調(diào)用retain時,isa.bits中的extra_rc+1
當(dāng)extra_rc溢出時耀鸦,sideTable.refcnts+1柬批。
而之前提到的TaggedPointer是不進(jìn)行引用計數(shù)的。

  1. retainCount方法


    image.png

    image.png

    返回extra_rc+1的值袖订,如果sidetable_rc有值再加上sideTable中的值氮帐。

3.release

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

    __sync_synchronize();
    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return true;
}

extra_rc - 1
當(dāng)extra_rc下溢出時,查找sideTable洛姑。如果sideTable也沒有時上沐,對this發(fā)送SEL_dealloc方法進(jìn)行析構(gòu)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末楞艾,一起剝皮案震驚了整個濱河市参咙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硫眯,老刑警劉巖蕴侧,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異两入,居然都是意外死亡净宵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門裹纳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來择葡,“玉大人,你說我怎么就攤上這事痊夭〉蟀叮” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵她我,是天一觀的道長虹曙。 經(jīng)常有香客問我,道長番舆,這世上最難降的妖魔是什么酝碳? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮恨狈,結(jié)果婚禮上疏哗,老公的妹妹穿的比我還像新娘。我一直安慰自己禾怠,他們只是感情好返奉,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布贝搁。 她就那樣靜靜地躺著,像睡著了一般芽偏。 火紅的嫁衣襯著肌膚如雪雷逆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天污尉,我揣著相機與錄音膀哲,去河邊找鬼。 笑死被碗,一個胖子當(dāng)著我的面吹牛某宪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锐朴,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼兴喂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了包颁?” 一聲冷哼從身側(cè)響起瞻想,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娩嚼,沒想到半個月后蘑险,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡岳悟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年佃迄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贵少。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡呵俏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滔灶,到底是詐尸還是另有隱情普碎,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布录平,位于F島的核電站麻车,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斗这。R本人自食惡果不足惜动猬,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望表箭。 院中可真熱鬧赁咙,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猿涨,卻和暖如春握童,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叛赚。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稽揭,地道東北人俺附。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像溪掀,于是被迫代替她去往敵國和親事镣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353