前言
我們知道內(nèi)存管理在任何一門編程語言中都有極其重要的地位误阻,即然極其重要行拢,也就意味著有難點(diǎn),今天我們就來剖析iOS的內(nèi)存管理相關(guān)的知識(shí)痕囱。
1 內(nèi)存五大區(qū)
內(nèi)核區(qū)田轧,用戶區(qū)。
用戶內(nèi)存五大區(qū):堆鞍恢、棧傻粘、bss(未初始化數(shù)據(jù))、data(已初始化數(shù)據(jù))帮掉、text(代碼段)弦悉。
- 棧區(qū):局部變量,方法參數(shù)旭寿,函數(shù)警绩,內(nèi)存地址一般為:0x7開頭
- 堆區(qū):通過alloc分配的對象崇败,block copy盅称,內(nèi)存地址一般為:0x6開頭
- BSS段:未初始化的全局變量肩祥,靜態(tài)變量,內(nèi)存地址一般為:0x1開頭
- 數(shù)據(jù)段: 初始化的全局變量缩膝,靜態(tài)變量混狠,內(nèi)存地址一般為:0x1開頭
- text:程序代碼,加載到內(nèi)存中
棧區(qū)的內(nèi)存是通過sp寄存器來定位疾层。
棧區(qū)的速度要比堆區(qū)速度快
2 內(nèi)存管理方案
內(nèi)存管理方案一般有:ARC/MRC
在ARC/MRC比較常見有taggedPointer将饺、NONPOINTER_ISA、散列表(引用計(jì)數(shù)表痛黎,弱引用表)
我們就來分析下這些內(nèi)存管理方案予弧。
2.1 taggedPointer底層分析
TaggedPointer:小對象-NSNumber,NSDate
TaggedPointer 是一個(gè)指針湖饱,會(huì)比普通的指針多了Tagged掖蛤,針對小對象,NSNumber井厌,NSDate蚓庭,NSString,UIColor仅仆,我們存這些對象的時(shí)候器赞,其實(shí)用不到64位,為了節(jié)省內(nèi)存空間墓拜,使用其部分位港柜,小對象=指針 + 存儲(chǔ)的內(nèi)容。
2.1.1 x86-64下的taggedPointer結(jié)構(gòu)
我們看以下代碼咳榜,來分析taggedPointer在x86上的結(jié)構(gòu)
代碼如下
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
- (void)indexPathDemo{
NSString *str = [NSString stringWithFormat:@"ro"];
NSLog(@"%p-%@-%@",str,str,str.class);
NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
}
運(yùn)行項(xiàng)目潘懊,如圖
我們知道堆區(qū)是0x7開頭,棧是0x6開頭贿衍,全局0x1開頭授舟,那么這個(gè)地址很奇怪,我們來分析下贸辈。
我們在objc源碼中搜下taggedPointer释树,如下所示
// payload = (decoded_obj << payload_lshift) >> payload_rshift
payload就是有效負(fù)載,經(jīng)過位運(yùn)算擎淤,加密和解密的過程奢啥。
我們分析一下decoded_obj這個(gè),源碼中嘴拢,搜索decoded桩盲,找到如下代碼
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
}
_objc_decodeTaggedPointer_noPermute的源碼如下
static inline uintptr_t
_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr)
{
uintptr_t value = (uintptr_t)ptr;
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return value;
#endif
return value ^ objc_debug_taggedpointer_obfuscator;
}
value ^ objc_debug_taggedpointer_obfuscator;這里進(jìn)行異或運(yùn)行,混淆席吴。
我們再找下objc_debug_taggedpointer_obfuscator這個(gè)赌结,
/***********************************************************************
* initializeTaggedPointerObfuscator
* Initialize objc_debug_taggedpointer_obfuscator with randomness.
*
* The tagged pointer obfuscator is intended to make it more difficult
* for an attacker to construct a particular object as a tagged pointer,
* in the presence of a buffer overflow or other write control over some
* memory. The obfuscator is XORed with the tagged pointers when setting
* or retrieving payload values. They are filled with randomness on first
* use.
**********************************************************************/
static void
initializeTaggedPointerObfuscator(void)
{
if (!DisableTaggedPointerObfuscation) {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
objc_debug_taggedpointer_obfuscator = 0;
}
}
initializeTaggedPointerObfuscator這里初始化
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
生成一個(gè)隨機(jī)數(shù)捞蛋,在整個(gè)內(nèi)存里是一個(gè)encode狀態(tài),它的真正地址是需要decode柬姚。
通過兩個(gè)異或(相同為0拟杉,不同為1)運(yùn)算,就可以得到真實(shí)的值量承。
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
所以這里我們通過這樣的異或運(yùn)算就可以得到真實(shí)的值搬设。
運(yùn)行項(xiàng)目,如圖
0xa00000000006f722就是真實(shí)的指針撕捍,我們解析下這個(gè)地址拿穴。
-
第1位的1代表是taggedPointer類型
我們再來看下面一張圖,如
4
114對應(yīng)o,111對應(yīng)r忧风,也就是我們的字符串贞言。
我們再改下代碼,如
NSString *str = [NSString stringWithFormat:@"ro"];
NSLog(@"%p-%@-%@",str,str,str.class);
NSLog(@"%p-%@ -%@- 0x%lx",str,str,str.class,ro_objc_decodeTaggedPointer(str));
NSString *str1 = [NSString stringWithFormat:@"r"];
NSLog(@"%p-%@ -%@- 0x%lx",str1,str1,str1.class,ro_objc_decodeTaggedPointer(str1));
NSString *str2 = [NSString stringWithFormat:@"robert"];
NSLog(@"%p-%@ -%@- 0x%lx",str2,str2,str2.class,ro_objc_decodeTaggedPointer(str2));
//
NSNumber *number = @6;
NSLog(@"%p-%@ -%@- 0x%lx",number,number,number.class,ro_objc_decodeTaggedPointer(number));
我們看下結(jié)果阀蒂,如圖
0xa對應(yīng)的是NSString,0xb對應(yīng)的是NSNumber该窗,我們來看下objc_tag
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
這里就不再一一驗(yàn)證了。
2.1.1 arm64下的taggedPointer
我們再來分析下真機(jī)下的效果蚤霞,相對于模擬器酗失,要復(fù)雜一些,代碼如下
#define kc_OBJC_TAG_INDEX_MASK 0x7UL
#define kc_OBJC_TAG_INDEX_SHIFT 0
extern uint8_t objc_debug_tag60_permutations[8];
uintptr_t ro_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
for (unsigned i = 0; i < 7; i++)
if (objc_debug_tag60_permutations[i] == tag)
return i;
return 7;
}
uintptr_t
ro_objc_decodeTaggedPointer(id ptr)
{
uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
value |= ro_objc_obfuscatedTagToBasicTag(basicTag) << kc_OBJC_TAG_INDEX_SHIFT;
return value;
}
static inline uintptr_t ro_objc_basicTagToObfuscatedTag(uintptr_t tag) {
return objc_debug_tag60_permutations[tag];
}
void *
ro_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
uintptr_t basicTag = (value >> kc_OBJC_TAG_INDEX_SHIFT) & kc_OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = ro_objc_basicTagToObfuscatedTag(basicTag);
value &= ~(kc_OBJC_TAG_INDEX_MASK << kc_OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << kc_OBJC_TAG_INDEX_SHIFT;
return (void *)value;
}
真機(jī)跟模擬器有不一樣的地方昧绣。
- 第1位是標(biāo)記taggedPointer
- 類型是存儲(chǔ)低三位 char 0规肴,short 1,int 2夜畴,3 long拖刃,4 float,5 double贪绘。
- 再住走四位兑牡,存儲(chǔ)的是數(shù)據(jù)的長度
- 再往后是數(shù)據(jù)的內(nèi)容
由于真機(jī)出現(xiàn)問題,暫時(shí)無法詳細(xì)寫出驗(yàn)證流程税灌,與模擬器原理相似均函。
2.1.1 taggedPointer面試題分析
//案例1
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.robert.ccom", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"robert"];
NSLog(@"%@",self.nameStr);
});
}
}
//案例2,連續(xù)點(diǎn)擊
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (int i = 0; i<100000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"3333333fdsdsfsafsafasfasfasfsafafafafda"];
NSLog(@"%@",self.nameStr);
});
}
}
以上代碼的運(yùn)行會(huì)不會(huì)產(chǎn)生崩潰菱涤?
答案是肯定的苞也。
分析如下:
- 案例1 taggedPointer類型的string
- 案例2 是普通的的string
- 案例2中會(huì)產(chǎn)生多線程的讀寫操作
- 案例2中會(huì)把setter方法操作,對新值的retain和舊值的release粘秆,所以在某一個(gè)瞬間會(huì)釋放多次如迟,產(chǎn)生對野指針的操作
- 案例2中NSCFString原因是因?yàn)閿?shù)據(jù)太長,taggedPointer針對小對象的,根本不受ARC的影響
部分源碼如下
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
if (slowpath(isTaggedPointer())) return (id)this;在這里如果是小針對直接返回殷勘,release同樣的道理此再,部分源碼如下
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
- 小對象的值直接存在指針里面,普通棧結(jié)構(gòu)的回收劳吠,根本不需在這里處理
- 在objc_msgSend中引润,如果是小對象根本不會(huì)進(jìn)行慢速查找流程
2.2 NONPOINTER_ISA
taggedPointer對類型的處理
NONPOINTER_ISA:非指針型isa
他們都會(huì)對位置進(jìn)行處理巩趁,如下
- 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ǔ)類指針的值(相當(dāng)于taggedPointer的payload)。開啟指針優(yōu)化的情況下别凹,在arm64架構(gòu)中有33位?來存儲(chǔ)類指針草讶。
- magic:?于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間
- weakly_referenced:志對象是否被指向或者曾經(jīng)指向?個(gè)ARC的弱變量,沒有弱引?的對象可以更快釋放炉菲。
- deallocating:標(biāo)志對象是否正在釋放內(nèi)存
- has_sidetable_rc:當(dāng)對象引?技術(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。
3 retain及release分析
3.1 MAR&ARC概念
ARC是LLVM和Runtime配合的結(jié)果道偷。
ARC中禁??動(dòng)調(diào)?retain/release/retainCount/dealloc
ARC新加了weak缀旁、strong屬性關(guān)鍵字
3.2 retain的流程分析
我們在源碼中搜下rootRetain,如下源碼
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return (id)this;
}
}
do {
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
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 (variant == RRVariant::Full) {
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();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
我們的引用計(jì)數(shù)存在isa里面勺鸦,也就是在extra_rc中并巍,在這里引用計(jì)數(shù)存滿時(shí),會(huì)放到散列表中换途。
reatain流程:
- if (slowpath(isTaggedPointer())) return (id)this;判斷是否是taggedPointer履澳,是的話直接返回
- isa_t oldisa;為了處理extra_rc,下面的do while循環(huán)是要著重分析的怀跛,
- if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
這里判斷是不是nonpointer距贷,如果是,isa位的操作 - 如果不是nonpointer吻谋,調(diào)用sidetable_retain這個(gè)函數(shù)忠蝗,
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
獲取SideTables的數(shù)據(jù),然后執(zhí)行+ 1<<2操作,也就是在原來的值上+2操作漓拾, 為什么是這樣的阁最,我們需要分析SideTables的結(jié)構(gòu)戒祠。
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)),WORD_BITS速种,如果是64位姜盈,左移1位,也就是63位配阵,2的二進(jìn)制是010馏颂,其它位置不變,2號(hào)位置+1操作棋傍,在2號(hào)位置存儲(chǔ)引用計(jì)數(shù)的值
- 如果是nonpointer救拉,執(zhí)行這段代碼
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
*判斷是不是在析構(gòu),如果在析構(gòu)就沒必要-1操作瘫拣,在多線程的情況下亿絮,已經(jīng)在釋放,還有可能-1麸拄。
- 接著在這里
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
在addc中派昧,拿到bits,執(zhí)行++操作拢切,# define RC_ONE (1ULL<<45)蒂萎,uintptr_t extra_rc : 19,extra_rc是在最后19位失球,剛好是64位岖是,左移45位剛好是extra_rc,然后執(zhí)行++1操作实苞,uintptr_t extra_rc : 8定義豺撑,說明extra_rc是8位,2的8次方256位黔牵,如果carry加多了聪轿,這個(gè)時(shí)候,執(zhí)行以下代碼
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
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;
}
在散列表中存儲(chǔ)猾浦,這里rootRetain_overflow調(diào)用這個(gè)函數(shù)判斷所有超載陆错。
接著
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
執(zhí)行這段代碼,打開鎖金赦,newisa.extra_rc = RC_HALF; #define RC_HALF (1ULL<<18)音瓷,左移18位,也就是移一半夹抗,把一半存在存在了extra_rc绳慎,另一半標(biāo)記為has_sidetable_rc,之后調(diào)用sidetable_addExtraRC_nolock這個(gè)函數(shù),加入到引用計(jì)數(shù)表中杏愤。
所以extra_rc存一半靡砌,散列表(引用計(jì)數(shù)表)存一半。
- extra_rc存在isa中珊楼,直接獲取到通殃,散列表需要先查表,size_t& refcntStorage = table.refcnts[this];找到相關(guān)對象存儲(chǔ)的區(qū)域厕宗,再移一半画舌。
通過散列表(引用計(jì)數(shù)表)增刪改查,同時(shí)加鎖解鎖媳瞪,是比較復(fù)雜骗炉,效率低照宝。
那為什么extra_rc還只要存一半蛇受?。 - 如果執(zhí)行release的--操作時(shí)厕鹃,如果全部存儲(chǔ)在散列表中兢仰,當(dāng)滿了的情況,去散列表(引用計(jì)數(shù)表)中去獲取剂碴,還是有問題把将,比如300,執(zhí)行-1操作忆矛,299察蹲,如果放在extra_rc是放不下的(extra_rc最值256),這樣操作比較耗費(fèi)性能催训。
- 所以各存一半洽议,在extra_rc存一半128位,也足夠執(zhí)行--操作了漫拭。
3.3 release的流程分析
我們在objc源碼搜索亚兄,經(jīng)過搜索找到如下源碼
bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return false;
}
}
retry:
do {
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// 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(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
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 (variant != RRVariant::Full) {
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.
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Try to remove some retain counts from the side table.
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (!stored && oldisa.nonpointer) {
// 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.
uintptr_t overflow;
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa.bits);
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa.bits);
goto retry;
}
// Decrement successful after borrowing from side table.
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- if (slowpath(isTaggedPointer())) return false;先判斷是不是TaggedPointer的話,直接返回不處理采驻。
- 在do while中
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
判斷如果不是nonpointer就執(zhí)行sidetable_release這個(gè)函數(shù)审胚,對散列表執(zhí)行release操作,源碼如下
uintptr_t
objc_object::sidetable_release(bool locked, bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
if (!locked) table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}
- 接著
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
判斷是不是在析構(gòu)礼旅。
- 如果是nonpointer膳叨,執(zhí)行以下代碼
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); //
執(zhí)行-1操作,如果
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
如果減多了痘系,跳轉(zhuǎn)到這里underflow菲嘴,如下
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
判斷是否有has_sidetable_rc,如果沒有跳轉(zhuǎn)到
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
這時(shí)給當(dāng)前對象發(fā)送一個(gè)dealloc消息。
- 如果有has_sidetable_rc临谱,執(zhí)行
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
這里代碼璃俗,取一半,然后
newisa.extra_rc = borrow.borrowed - 1;
執(zhí)行--操作悉默。
- 在sidetable_subExtraRC_nolock函數(shù)中
objc_object::SidetableBorrow
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
ASSERT(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return { 0, 0 };
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT };
}
散列中存一半城豁。
3.4 dealloc簡單基本流程概述
- 根據(jù)當(dāng)前對象的狀態(tài)是否直接調(diào)用free()釋放
- 是否在瞎了眼在C++的析構(gòu)函數(shù),移除這個(gè)對象的關(guān)聯(lián)屬性
- 將指向該對象的弱引用指針置為nil
- 從弱引用表中移除該對象的引用計(jì)數(shù)
總結(jié)
這次我們介紹了內(nèi)存的五大區(qū)抄课,taggedPointer唱星,retain,release的底層分析跟磨,希望些文章可以讓大家對iOS的內(nèi)存管理有一個(gè)新的認(rèn)識(shí)间聊,該文章略有粗糙,還望諒解抵拘。