iOS 的內(nèi)存管理

面試的時(shí)候幾乎都會(huì)被問(wèn), 是個(gè)比較大的問(wèn)題. 整理了一些可以聊的點(diǎn).

引用計(jì)數(shù)器, ARC 和 MRC

引用計(jì)數(shù)器: 通過(guò)引用計(jì)數(shù)決定一個(gè)引用是否需要釋放.
ARC: 由編譯器幫忙完成引用計(jì)數(shù)的增減.
MRC: 由開(kāi)發(fā)者手動(dòng)完成引用計(jì)數(shù)的增減.

Tagged Pointer

由于64位 CPU 的出現(xiàn), 部分類型Int 型的內(nèi)存占用會(huì)翻倍(值大小翻倍), 而一個(gè)存儲(chǔ)了 Int 型數(shù)據(jù)的 NSNumber 對(duì)象則從原來(lái)的8字節(jié)增加到16字節(jié). (指針位數(shù)翻倍).
Tagged Pointer 的出現(xiàn)就是為了針對(duì)這類情況, 對(duì)指針的內(nèi)存占用進(jìn)行壓縮.(比如一個(gè) NSNumber 存儲(chǔ)了一個(gè)值為1024的 Int 型, Tagged Pointer的內(nèi)存地址值會(huì)完整包含1024, 而不需要額外的內(nèi)存去存儲(chǔ)1024這個(gè)數(shù)據(jù)).

NONPOINTER_ISA

64位系統(tǒng)下的優(yōu)化指針, 攜帶內(nèi)容除了 對(duì)象內(nèi)存地址, 還有 是否優(yōu)化指針標(biāo)記/引用計(jì)數(shù)/是否有弱引用指向/是否正在釋放等信息

NONPOINTER_ISA和散列表

AutoReleasePool

干預(yù) ARC 環(huán)境下引用對(duì)象的 release 時(shí)機(jī), 使它在運(yùn)行語(yǔ)句離開(kāi)AutoReleasePool后執(zhí)行.

引用表SideTables(強(qiáng)/弱引用)

弱引用不會(huì)對(duì)指向?qū)ο笤斐蓃etain, runtime 維護(hù)了一個(gè)被稱為 SideTablesHashMap<Int, SideTable> 用于管理內(nèi)存地址的引用計(jì)數(shù)和弱引用, 這個(gè) HashMap 的 Key 就是引用的地址了, 而 Value 就是一個(gè)用于管理這個(gè)內(nèi)存地址對(duì)應(yīng)的引用的類SideTable.

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

    // Address-ordered lock discipline for a pair of side tables.

    template<bool HaveOld, bool HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<bool HaveOld, bool HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

SideTable結(jié)構(gòu)中有:

  • 自旋鎖 spinlock_t, 在操作 SideTable 的時(shí)候上鎖.(為了可以同時(shí)對(duì)多個(gè)地址進(jìn)行操作, 使用分離鎖)
  • RefcountMap, 管理內(nèi)存地址的引用計(jì)數(shù).
  • weak_table_t, 管理內(nèi)存地址的弱引用.

對(duì)象的內(nèi)存管理操作

  1. 當(dāng)聲明一個(gè)強(qiáng)引用ref指向?qū)ο?code>instance的時(shí)候:
  • 會(huì)在SideTables中找到對(duì)應(yīng) instance內(nèi)存地址的SideTable, 操作RefcountMap, 根據(jù)情況(是否已經(jīng)到達(dá)上限)增加引用計(jì)數(shù).
  1. 當(dāng)聲明一個(gè)弱引用weak_ref指向某個(gè)對(duì)象instance的時(shí)候:
  • 會(huì)生成一個(gè)新的引用weak_ref, 指向這個(gè)對(duì)象的地址, 引用weak_ref會(huì)被放進(jìn)SideTables里對(duì)應(yīng)instance內(nèi)存地址的弱引用表weak_table_t中. 這個(gè)過(guò)程中, 對(duì)象weak_ref的引用計(jì)數(shù)器不會(huì)增加.
  • 操作對(duì)應(yīng) instanceRefcountMap, 標(biāo)記為有弱引用指向.
  1. 當(dāng)一個(gè)強(qiáng)引用ref 不再指向?qū)ο?instance 的時(shí)候:
  • 查找對(duì)應(yīng) instanceRefcountMap , 根據(jù)情況(是否正在釋放)減少引用計(jì)數(shù).
  • 如果instance引用計(jì)數(shù)已經(jīng)為0, 則開(kāi)始對(duì)instance進(jìn)行釋放.
  1. 當(dāng)對(duì)象instance要被釋放的時(shí)候:
  • 操作對(duì)應(yīng)的RefcountMap, 標(biāo)記目標(biāo)地址正在被釋放.
  • 根據(jù)情況(是否標(biāo)記為有弱引用指向)在引用表weak_table_t中找到指向instance的所有弱引用, 把這些引用設(shè)置成nil.

內(nèi)存對(duì)齊

iOS 中數(shù)據(jù)結(jié)構(gòu)的內(nèi)存占用遵循以下規(guī)則

  1. 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第
    一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要
    從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說(shuō)是數(shù)組,
    結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(比如int為4字節(jié),則要從4的整數(shù)倍地址開(kāi)始存
    儲(chǔ)。
  2. 結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
    其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ).(struct a里存有struct b,b
    里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開(kāi)始存儲(chǔ).)
  3. 收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,.必須是其內(nèi)部最大
    成員的整數(shù)倍.不足的要補(bǔ)?。

值類型和引用類型

值類型: 拷貝數(shù)據(jù), 不需要考慮多線程操作沖突.
引用型: 拷貝指針地址, 需要考慮內(nèi)存釋放.

堆和棧

堆的容量大, 存放引用類型, 內(nèi)存由開(kāi)發(fā)者自己申請(qǐng)/釋放, 速度相對(duì)于棧更慢.
棧的容量有限, 存放值類型, 內(nèi)存由系統(tǒng)管理, 操作速度快.

深拷貝和淺拷貝

深拷貝: 開(kāi)辟新的內(nèi)存空間, 創(chuàng)建一個(gè)內(nèi)容和被拷貝對(duì)象一樣的新對(duì)象.
淺拷貝: 只是拷貝一份被拷貝對(duì)象的內(nèi)存地址, 相當(dāng)于創(chuàng)建一個(gè)指向被拷貝對(duì)象的指針.

函數(shù)派發(fā)

函數(shù)派發(fā)有3種:

  1. 靜態(tài)派發(fā)
  • 編譯期間調(diào)用的函數(shù)必須已經(jīng)實(shí)現(xiàn).
  • 在編譯時(shí)就決定了調(diào)用哪個(gè)函數(shù), 運(yùn)行時(shí)不可更改.
  • 調(diào)用性能最好(編譯時(shí)可以針對(duì)優(yōu)化).
  1. 函數(shù)表派發(fā)
  • 內(nèi)存里, 對(duì)每一個(gè) class 建立一個(gè)函數(shù)表, 用于存儲(chǔ)這個(gè)類的函數(shù)所在內(nèi)存位置.
  • 子類函數(shù)表中包含:
    1. 所有從父類中繼承的函數(shù), 函數(shù)的內(nèi)存地址和父類中相同函數(shù)的地址相同.
    2. 子類新增的函數(shù).
  • 在子類的函數(shù)表里, 子類重寫(xiě)的函數(shù)會(huì)替換父類的函數(shù)(使用新的內(nèi)存地址).
  • 調(diào)用性能弱于靜態(tài)派發(fā)方式(需要操作指針在表中查找函數(shù)內(nèi)存地址).
  1. 消息派發(fā)
  • 一個(gè)消息發(fā)送給一個(gè)對(duì)象的時(shí)候, runtime 會(huì)針對(duì)這個(gè)對(duì)象構(gòu)建一個(gè)樹(shù).
  • 樹(shù)的每一層包含:
    1. 該類自己實(shí)現(xiàn)的函數(shù).
    2. 該類上一層父類的指針(如果存在).
  • runtime 從樹(shù)的根部開(kāi)始查找消息名稱對(duì)應(yīng)的函數(shù), 如果找不到當(dāng)層就往下一層查找.
  • 如果到樹(shù)的末端依然找不到對(duì)應(yīng)的函數(shù), 也沒(méi)有實(shí)現(xiàn)消息重定向, 就會(huì)報(bào)錯(cuò)導(dǎo)致程序崩潰.
  • 在調(diào)用之前仍可以不確定(不實(shí)現(xiàn))具體執(zhí)行函數(shù), 但是調(diào)用的時(shí)候必須確定.
  • 在運(yùn)行期間, 函數(shù)可以更換.
  • 調(diào)用性能較差(每次調(diào)用都需要重新確定).

Swift靜態(tài)派發(fā)場(chǎng)景:

  1. 值類型的所有函數(shù)
  2. static 或者 final 修飾的函數(shù)
  3. extension 內(nèi)聲明的@objcdynamic修飾函數(shù)

Swift函數(shù)表派發(fā)場(chǎng)景:

  1. class 內(nèi)聲明@objcdynamic修飾的函數(shù)
  2. protocol 內(nèi)聲明@objcdynamic修飾的函數(shù)

Swift消息派發(fā)場(chǎng)景:

  1. @objc 修飾的函數(shù)
  2. dynamic 修飾的函數(shù)

weak, __weak, __block, unowned

copy on write

參考:
Swift進(jìn)階之內(nèi)存模型和方法調(diào)度
iOS weak的底層實(shí)現(xiàn)
iOS-內(nèi)存對(duì)齊
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法
Method Dispatch in Swift
深入理解Tagged Pointer
NONPOINTER_ISA和散列表

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 什么是內(nèi)存管理?是指軟件運(yùn)行時(shí)對(duì)計(jì)算機(jī)內(nèi)存資源的分配和使用的技術(shù)。其最主要的目的是如何高效,快速的分配,并且...
    愛(ài)好技術(shù)的小白閱讀 203評(píng)論 0 0
  • 參考:Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理 程序的內(nèi)存分配:1、棧區(qū)(stack)—由...
    coder_my閱讀 1,891評(píng)論 0 2
  • 從這篇文章開(kāi)始探索iOS的內(nèi)存管理,主要涉及的內(nèi)容有1. 內(nèi)存布局;2. 內(nèi)存管理方案:Tagged Pointe...
    風(fēng)緊扯呼閱讀 1,675評(píng)論 1 16
  • iOS內(nèi)存管理需要了解這幾個(gè)方面: 內(nèi)存布局 引用計(jì)數(shù) 自動(dòng)釋放池 循環(huán)引用和core foundation對(duì)象的...
    boy丿log閱讀 774評(píng)論 0 0
  • 1、內(nèi)存布局 stack:方法調(diào)用 heap:通過(guò)alloc等分配對(duì)象 bss:未初始化的全局變量等。 data:...
    AKyS佐毅閱讀 1,722評(píng)論 0 19

友情鏈接更多精彩內(nèi)容