1.內(nèi)存布局
棧區(qū) 0x7
創(chuàng)建臨時(shí)變量時(shí)由編譯器自動(dòng)分配互婿,在不需要的時(shí)候自動(dòng)清除的變量的存儲(chǔ)區(qū)呛牲。
里面的變量通常是局部變量娘扩、函數(shù)參數(shù)等琐旁。在一個(gè)進(jìn)程中灰殴,位于用戶虛擬地址空間頂部的是用戶棧牺陶,編譯器用它來實(shí)現(xiàn)函數(shù)的調(diào)用。和堆一樣辣之,用戶棧在程序執(zhí)行期間可以動(dòng)態(tài)地?cái)U(kuò)展和收縮掰伸。
堆區(qū) 0x6
那些由 new alloc 創(chuàng)建的對象所分配的內(nèi)存塊,它們的釋放系統(tǒng)不會(huì)主動(dòng)去管怀估,由我們的開發(fā)者去告訴系統(tǒng)什么時(shí)候釋放這塊內(nèi)存(一個(gè)對象引用計(jì)數(shù)為0是系統(tǒng)就會(huì)回銷毀該內(nèi)存區(qū)域?qū)ο?狮鸭。一般一個(gè) new 就要對應(yīng)一個(gè) release。在ARC下編譯器會(huì)自動(dòng)在合適位置為OC對象添加release操作奏夫。會(huì)在當(dāng)前線程Runloop退出或休眠時(shí)銷毀這些對象怕篷,MRC則需程序員手動(dòng)釋放历筝。
堆可以動(dòng)態(tài)地?cái)U(kuò)展和收縮麻削。
靜態(tài)區(qū)(未初始化數(shù)據(jù)).bss
程序運(yùn)行過程內(nèi)存的數(shù)據(jù)一直存在叠荠,程序結(jié)束后由系統(tǒng)釋放
常量區(qū)(已初始化數(shù)據(jù)).data
專門用于存放常量鳖孤,程序結(jié)束后由系統(tǒng)釋放
代碼區(qū)
用于存放程序運(yùn)行時(shí)的代碼,代碼會(huì)被編譯成二進(jìn)制存進(jìn)內(nèi)存的程序代碼區(qū)
1.堆棧溢出的原因:
堆里面的放new出來的變量,一直往高地址延伸
棧區(qū)里面放了一些函數(shù),方法以及臨時(shí)變量,一直往低地址延伸
當(dāng)前APP進(jìn)程分配的內(nèi)存有限,會(huì)有一個(gè)臨界點(diǎn)
當(dāng)這兩個(gè)相遇就會(huì)形成堆棧溢出
2.內(nèi)存管理方案
TaggedPointer:小對象-NSNumber NSDate
- 存儲(chǔ)小對象
- 不是一個(gè)簡單的地址 包含了值和類型以及長度
例如: 0x00000000012 (1代表值 2代表類型)- 速度特別快
NONPOINTER_ISA (非指針型isa)
是聯(lián)合體,被優(yōu)化成聯(lián)合體的位域 每一段里面都代表不同的含義
在不同的架構(gòu)下具體的分配是不一樣的
如果你重寫了allocWithZone后沒有調(diào)用父類 就不是被優(yōu)化過的ISA了
nonpointer: 表示是否對 isa 指針開啟指針優(yōu)化
0:純isa指針
1:不止是類對象地址,isa 中包含了了類信息忍燥、對象的引?用計(jì)數(shù)等
has_assoc: 關(guān)聯(lián)對象標(biāo)志位哎甲,
0沒有
1存在
has_cxx_dtor: 該對象是否有 C++ 或者 Objc 的析構(gòu)器?,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更更快的釋放對象
shiftcls: 存儲(chǔ)類指針的值。開啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位?用來存儲(chǔ)類指針
magic: 用于調(diào)試?判斷當(dāng)前對象是真的對象還是沒有初始化的空間 weakly_referenced: 標(biāo)志對象是否被指向或者曾經(jīng)指向?一個(gè) ARC 的弱變量,沒有弱引用的對象可以更快釋放。
deallocating: 標(biāo)志對象是否正在釋放內(nèi)存
has_sidetable_rc:當(dāng)對象引?計(jì)數(shù)大于 10 時(shí)德崭,則需要借?該變量存儲(chǔ)進(jìn)位
extra_rc:當(dāng)表示該對象的引?計(jì)數(shù)值兽狭,實(shí)際上是引用計(jì)數(shù)值減 1, 例如有咨,如果對象的引用計(jì)數(shù)為 10似忧,那么 extra_rc 為 9。如果引?用計(jì)數(shù)?大于 10, 則需要使用到上面的 has_sidetable_rc。
散列表(SideTable):
SideTable 其實(shí)是一個(gè) hash 表梢睛,下面掛了很多的 SideTable藏畅,SideTable 包括自旋鎖(Spinlock_t)航瞭,引用計(jì)數(shù)表(RefCountMap)滨彻,弱引用表(weak_table_t)。
SideTable 為什么是多張表,而不是一張表?:
如果只有一張表,如果想操作某一個(gè)對象的引用計(jì)數(shù),由于不同的對象是在不同的線程操作饮醇,由于不同線程需要來操作這張表祠墅,所以就有資源訪問的問題回铛,那么就需要對這張大表進(jìn)行加鎖操作,如果成千上萬對自己進(jìn)行引用計(jì)數(shù)操作巾乳,那么需要加鎖排隊(duì)压状,就會(huì)有效率問題碌廓,所以系統(tǒng)引用了 “分離鎖” 概念,比如 A玛臂,B同時(shí)進(jìn)行操作的話橱鹏,可以并發(fā)進(jìn)行杉辙,因?yàn)锳并淋,B唾琼,在不同的表中。
如果實(shí)現(xiàn)快速分流绞佩?找到當(dāng)前對象在哪張表中?:
SideTable 其實(shí)是一張 hash 表,key(對象指針)->hash函數(shù)->value(SideTable),通過這個(gè)hash計(jì)算之后咙好,就可以計(jì)算出當(dāng)前對象在哪個(gè)hash表中,也就找到了對應(yīng)的sideTable
hash 查找:
給定一個(gè)內(nèi)存地址抖苦,通過hash計(jì)算就可以得到數(shù)組的下標(biāo)地址,f(ptr) = ptr%arr.count商膊,比如內(nèi)存地址為1堤器,通過上面的就可以找到在數(shù)組中的位置
散列表的數(shù)據(jù)結(jié)構(gòu):
Spinlock_t:自旋鎖
忙等乓旗,如果鎖已被其他線程獲取扰法,那么當(dāng)前線程會(huì)自己去不斷的獲取是否被釋放,直到其他線程釋放沸伏,適用于輕量訪問,如+1慧脱,-1染苛。RefCountMap:引用計(jì)數(shù)表
其實(shí)就是hash查找,提高查找效率呻此,插入和查找通過同一個(gè)hash函數(shù)來獲取贝乎,避免了循環(huán)遍歷。ptr->hash->size_t,其中的size_t就是引用計(jì)數(shù)值术裸,比如用64位存儲(chǔ)答倡,第一位表示(weakly_referenced),表示對象是否存在弱引用笨篷,下一位表示當(dāng)前對象是都正在dealloc(deallocating)辜贵,剩下的位表示引用計(jì)數(shù)值窟感。weak_table_t:弱引用表
也是一個(gè)hash表重荠,key->hash->weak_entry_t,weak_entry_t,其實(shí)是一個(gè)結(jié)構(gòu)體數(shù)組(weakPtr)罩扇,比如被weak修飾集侯,就存在這個(gè)弱引用表中泡挺。
3.ARC&MRC
4.引用計(jì)數(shù)
1. alloc出來引用計(jì)數(shù)
Person *p = [Person alloc]; // extrac = 0 NSLog(@"%lu",(unsigned long)[p retainCount]); // 1媳溺,原理如下圖所示??
返回引用計(jì)數(shù)+1 所有打印的retainCount至少都是1
2.retain原理
執(zhí)行順序
<1> - (id)retain {}
<2> objc_object::rootRetain()參數(shù)分別:false,false
<3> objc_object::rootRetain(bool tryRetain, bool handleOverflow)
<4> 判斷新舊isa是否一致循環(huán)始藕,一致就執(zhí)行<9>扩借,否則執(zhí)行<5>
<5> 循環(huán)獲取舊值椒惨,并賦給新值,為新值進(jìn)行extra_rc+1
<6> 判斷是否溢出(x86_64 256)潮罪,沒溢出就執(zhí)行<9>康谆,溢出走<7>
<7> 執(zhí)行rootRetain_overflow领斥,回到<3>,handleOverflow為true沃暗,下次過來時(shí)執(zhí)行<8>
<8> x86_64留下引用計(jì)數(shù)的一半128月洛,復(fù)制另一半存進(jìn)去散列表
<9> return
// 并且調(diào)用retain的時(shí)候,傳入的兩個(gè)參數(shù)均為false
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
// 循環(huán)條件:判斷是否獨(dú)一份存儲(chǔ)孽锥,對比新舊isa嚼黔,如果不是,就循環(huán)
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
// 如果當(dāng)前對象的isa 正在銷毀
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
//是否溢出惜辑,
//經(jīng)過實(shí)驗(yàn):在x86_64架構(gòu)下,當(dāng)newisa.extra_rc為255時(shí)唬涧,在進(jìn)行addc,就會(huì)發(fā)生溢出
//溢出之后盛撑,將會(huì)拿2的7次方的extra_rc 存到散列表中碎节,newisa.extra_rc回到128
uintptr_t carry;
//這里newisa.extra_rc 會(huì)+1 RC_ONE
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
printf("%lu,",newisa.extra_rc);
//newisa.extra_rc++如果溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//第一次來的話,handleOverflow是false抵卫,會(huì)進(jìn)判斷語句
if (!handleOverflow) {
ClearExclusive(&isa.bits);
//這里重新調(diào)用了當(dāng)前方法rootRetain钓株,但是handleOverflow = true
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
// retry之后會(huì)來到這里
// 翻譯:留下內(nèi)部關(guān)聯(lián)對象的一半,準(zhǔn)備復(fù)制另一半存進(jìn)去散列表
if (!tryRetain && !sideTableLocked) {
sidetable_lock();
}
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
//當(dāng)且僅當(dāng)舊值與存儲(chǔ)中的當(dāng)前值一致時(shí)陌僵,才把新值寫入存儲(chǔ)轴合。
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
// 拷貝一半(128)進(jìn)散列表
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
3.release
執(zhí)行順序
<1> - (oneway void)release {}
<2> objc_object::rootRelease() 參數(shù)分別:true,false
<3> objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
<4> 判斷新舊isa是否一致循環(huán),一致就執(zhí)行return碗短,否則執(zhí)行<5>
<5> 循環(huán)獲取舊值受葛,并賦給新值,為新值進(jìn)行extra_rc-1
<6> 判斷是否溢出偎谁,沒溢出就執(zhí)行return总滩,溢出走<7> underflow
<7> 判斷是否有用到散列表
<8> 從散列表中拿出RC_HALF,將這部分存進(jìn)newisa
<9> 存成功就return巡雨,不成功就重試闰渔,再不行就把拿出來的放回去,然后goto retry;
<10> dealloc
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
//新舊isa
isa_t oldisa;
isa_t newisa;
retry:
//跟retain一樣的判斷條件
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.extra_rc-1
//如果溢出的時(shí)候铐望, newisa.extra_rc = 255
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
//如果溢出走這
printf("釋放溢出了,underflow\n");
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
// 重新把舊isa給新isa冈涧,意思是把引用計(jì)數(shù)-1操作還原
// 這時(shí)候的 newisa.extra_rc = 0
newisa = oldisa;
// retain的時(shí)候。如果有用到散列表正蛙,會(huì) newisa.has_sidetable_rc = true;
if (slowpath(newisa.has_sidetable_rc)) {
printf("發(fā)現(xiàn)has_sidetable_rc = true \n");
// 調(diào)用release的時(shí)候handleUnderflow = false
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
//類似retain時(shí)候retry督弓,重新來一次,但是handleUnderflow為true
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
// 進(jìn)判斷前 sideTableLocked 沒有重新賦值乒验,所以一直是false
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
// 去retry愚隧,重新回到上面,重復(fù)走一遍
goto retry;
}
// Try to remove some retain counts from the side table.
// 從散列表中拿出RC_HALF的引用計(jì)數(shù)
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
printf("借出來的 size === %lu \n",borrowed);
// 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
// 把拿出來的引用計(jì)數(shù)存到newisa
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
//如果沒存成功锻全,就換個(gè)姿勢再試試
// 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.
// 如果newisa.has_sidetable_rc != true;
// 就拋錯(cuò)录煤,release太多
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;
}
retain、release總結(jié)
現(xiàn)在isa是NONPOINTER_ISA,他是按位進(jìn)行存儲(chǔ)的荞胡,我們引用計(jì)數(shù)的存儲(chǔ)在2個(gè)位置妈踊,一個(gè)在extra_rc,第二個(gè)在散列表里面硝训。
retain:是會(huì)+1的 extra_rc + 1响委。extra_rc只有8個(gè)位置(X86下)當(dāng)超出的時(shí)候就會(huì)carry 也就是上溢出 就會(huì)把1半的空間往散列表里面丟,如果散列表里面都滿了那么這個(gè)時(shí)候會(huì)發(fā)生容量的變化窖梁。
relase: extra_rc -1 赘风,如果extra_rc為0的時(shí)候,在減1的時(shí)候就會(huì)是下溢出纵刘,首先會(huì)判斷散列表邀窃,如果散列表里面會(huì)借用一半的RC_HALF-1 存到extra_rc里面,如果散列表里面也沒有假哎,就會(huì)發(fā)生下溢出瞬捕,就觸發(fā)析構(gòu)函數(shù)。
- dealloc
應(yīng)該做一些什么事情舵抹?
free函數(shù) - 釋放對象
weak弱引用計(jì)數(shù)表 - 處理
關(guān)聯(lián)對象 associated
5.弱引用
不操作引用計(jì)數(shù)
散列表里面有weak 對象
1:weak_register_no_lock
2:從我們的散列表的 weak_table 哈希表—>weak_entry_t *entry;
3: weak_entry_t *weak_entries 下標(biāo) 下的 entry
4:entry->inline_referrers[i] = new_referrer;
5:new_referrers[i] = entry->inline_referrers[i]
6:entry->referrers = new_referrers;
總結(jié)
sidetabels — sidetable - 弱引用表
weakTable - entry - 數(shù)組 - 弱引用對象指針
更詳細(xì)的解析
6.自動(dòng)釋放池
什么時(shí)候用到
自動(dòng)釋放池 : 容納變量 - 釋放
1:大量的臨時(shí)變量
2:非UI操作, 命令行
3:自己創(chuàng)建輔助線程
原理
__AtAutoreleasePool __autoreleasepool;
objc_autoreleasePoolPush --> atautoreleasepoolobj
objc_autoreleasePoolPop
AutoreleasePoolPage::push();
page:屬性 56個(gè)字節(jié)
一頁所能容乃的大小: 4096
// 505 滿
// 38 = 3*16+8 = 48+8 = 56
// 16進(jìn)制 8
// 0x103803038 邊界 -- 加進(jìn)去 壓棧 -- 剪出來 出棧 - 頁面銷毀
自動(dòng)釋放池 - 雙向鏈表
這一頁是不是滿 --
4096 - 屬性 56 push 壓棧 + 邊界符 - 開辟新的頁面 壓棧