先來了解一下iOS中的內(nèi)存布局兰粉。
上面的圖代表的是內(nèi)存區(qū)域扮惦,最上方是內(nèi)核區(qū),最下面是保留的內(nèi)存空間亲桦。中間位置是給程序加載使用的空間。程序被加載到內(nèi)存浊仆,會分為三部分客峭。
- 未初始化數(shù)據(jù)(.bss),未初始化的靜態(tài)變量抡柿,全局變量等
- 已初始化數(shù)據(jù)(.data)舔琅,已初始化的靜態(tài)變量,全局變量等
- 代碼段(.text)洲劣,程序代碼就存在這個區(qū)域
iOS中定義的一些方法函數(shù)都是在棧上進行工作的备蚓,棧是從高地址到低地址擴展。而對象囱稽,或者是被copy的block都存在于堆中郊尝,推是由低地址向高地址擴展的。
棧(satck)
: 方法調(diào)用主要在這個內(nèi)存區(qū)域中進行
堆(heap)
: 通過alloc等分配的對象存在于這個區(qū)域
未初始化數(shù)據(jù)(.bss)
: 未初始化的靜態(tài)變量战惊,全局變量等
已初始化數(shù)據(jù)(.data)
: 已初始化的靜態(tài)變量流昏,全局變量等
代碼段(.text)
: 程序代碼存放區(qū)域
內(nèi)存管理方案
在iOS中,內(nèi)存管理方案分為以下幾種:
- TaggedPointer(小對象吞获,如NSNumber等)
- NONPOINTER_ISA(64位架構(gòu)下的內(nèi)存管理方案况凉,通常內(nèi)存中表示地址只需要32-40位即可,多出的區(qū)域用來存儲其他的數(shù)據(jù)各拷,以節(jié)省內(nèi)存)
- 散列表
- 引用計數(shù)表
- 弱引用表
- ...
NONPOINTER_ISA
在arm64架構(gòu)下刁绒,我們來看看NONPOINTER_ISA所表示的是怎樣的結(jié)構(gòu)
- 第1位。是一個叫indexed的標志位烤黍,如果為0知市,表示這個isa指針是一個純的地址指針(保存的都是類對象的地址)傻盟。1則表示這個isa指針除去類對象地址外還保存著一些關于內(nèi)存管理的其他的數(shù)據(jù)。
- 第2位初狰。has_assoc,表示當前對象是否有關聯(lián)對象莫杈,0代表沒有。
- 第3位奢入。has_cxx_dtor筝闹,代表當前對象是否有使用到C++相關的內(nèi)容。0表示沒有腥光。
- 第4-35位关顷。shiftcls,這33位都保存著isa所指向的類對象的指針地址武福。
- 第36-41位议双。magic,
- 第42位捉片。weakly_referenced平痰,用來標識對象是否有相應的弱引用指針。
- 第43位伍纫。deallocating宗雇,用來標識對象是否正在被銷毀。
- 第44位莹规。has_sidetable_rc赔蒲,表示當前isa指針當中存儲的引用計數(shù)已經(jīng)達到了上限,則需要外掛一個sidetable(散列表)的數(shù)據(jù)結(jié)構(gòu)來存儲相關的引用計數(shù)內(nèi)容良漱。
- 第45-63位舞虱。extra_rc,存儲的就是當前isa指針的引用計數(shù)(在引用計數(shù)不夠大時母市,會使用這塊區(qū)域矾兜,太大就外掛散列表)。
散列表
下面就來看看SideTables的結(jié)構(gòu)
// 這是一個Hash表
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
我們可以看到患久,這下面掛了很多SideTable
結(jié)構(gòu)體焕刮。
再來看看SideTable
里面是什么
struct SideTable {
spinlock_t slock; // 自旋鎖
RefcountMap refcnts; // 引用計數(shù)表
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);
};
先來思考一個問題,為什么要使用SideTables
墙杯,而不是一個SideTable
來表示呢配并?
如果說,將所有的引用計數(shù)高镐,都放在一張大表中溉旋,那么我們對某個對象的引用計數(shù)進行操作時,會進行加鎖的操作嫉髓。同一時刻只有一個線程可以訪問到這個表观腊,這樣其他的線程就會造成阻塞邑闲。等待持有這個鎖的線程釋放才可以進行引用計數(shù)表的操作。但是梧油,如果說有多張表苫耸,就可以一定程度上解決這個問題。而這種解決方案叫做分離鎖
儡陨。也就是將一個大的共享資源褪子,拆分成多個,并分別加鎖骗村。
那么嫌褪,問題又來了,如何實現(xiàn)快速分流(如何快速的通過對象指針找到對象到底是屬于哪個SideTable)胚股?
首先笼痛,SideTables的本質(zhì)是一張Hash表。對象指針通過Hash函數(shù)的計算琅拌,會計算出一個值缨伊。來決定對象鎖對應的SideTable是哪張。
SideTable
現(xiàn)在我們來剖析一下SideTable的數(shù)據(jù)結(jié)構(gòu):
- spinlock_t
- RefcountMap
- weak_table_t
spinlock_t(自旋鎖):
- spinlock_t是
忙等
(如果鎖被其他線程獲取进宝,當前線程會不斷的探測鎖是否被釋放)的鎖 - 適用于輕量訪問(簡單的計算)
RefcountMap(引用計數(shù)表):
這也是一個Hash表倘核,通過指針可以找到對象的引用計數(shù)
這里說明一下為什么用Hash表,Hash表結(jié)構(gòu)的本質(zhì)即彪,是將數(shù)據(jù)插入數(shù)組時,使用函數(shù)計算其位置活尊,取出時也通過這個函數(shù)隶校,取得位置,避免了大量的遍歷操作蛹锰,從而提升效率深胳。
size_t
是一個unsign long
型的變量。我們再來看看其內(nèi)存的存儲結(jié)構(gòu):
第一位代表是否有弱引用铜犬。第二位表示當前對象是否在進行銷毀舞终。后面的才是引用技術計數(shù)值。因此癣猾,我們在獲取對象的引用計數(shù)值的時候敛劝,需要向右偏移兩位,才可以取得真實的引用計數(shù)值纷宇。
weak_table_t(弱引用表)
這也是一個Hash表夸盟,通過指針可以找到對象在表中的存儲位置。
weak_entry_t是一個結(jié)構(gòu)體數(shù)組像捶。其中存儲的就是實際的弱引用指針上陕。