內(nèi)存概述
????????內(nèi)存是用來(lái)存啥的本辐?
?? ? ? ?內(nèi)存布局
????????哈希表
?????????垃圾回收(GC)
IOS內(nèi)存管理機(jī)制
????????MRC & ARC
????????TaggedPointer & NONPOINTER_ISA
????????引用計(jì)數(shù)表?&?弱引用表
????????自動(dòng)釋放池
循環(huán)引用
????????分類
????????如何破除循環(huán)引用
????????循環(huán)引用實(shí)例
要記錄的都列出來(lái)了,下面就切入正文了。。。
內(nèi)存概述
? ? ? ? ?1. 內(nèi)存是用來(lái)存啥的呢橱赠?
? ???????????答案:指令+數(shù)據(jù)厚棵!
????????2. 內(nèi)存布局(對(duì)于程序運(yùn)行過(guò)程中的內(nèi)存使用蕉世,堆和棧一般是相向擴(kuò)展的)
? ??????????????內(nèi)核區(qū):命令行參數(shù),環(huán)境變量等婆硬。
? ??????????????棧:編譯的時(shí)候能確定好的狠轻。函數(shù)內(nèi)部使用的變量、函數(shù)的參數(shù)以及返回值彬犯;由編譯器自動(dòng)分配和釋放向楼;后進(jìn)先出;從高往低分配躏嚎。
? ??????????????堆:編譯時(shí)不能提前確定蜜自,通常堆中的對(duì)象都是以指針來(lái)訪問(wèn)的菩貌,指針從線程棧中來(lái)卢佣,但不獨(dú)屬于某個(gè)線程。它的大小并不固定箭阶,可動(dòng)態(tài)擴(kuò)張或縮減虚茶。一般由程序員分配和釋放。動(dòng)態(tài)調(diào)用malloc和free來(lái)分配和釋放內(nèi)存仇参;從低往高分配嘹叫。
? ??????????????數(shù)據(jù):.BSS(未初始化的全局變量、靜態(tài)變量诈乒,系統(tǒng)初始化為0)罩扇;RW data(已初始化的全局變量、靜態(tài)變量)怕磨;RO data(常量)?喂饥。
? ??????????????代碼:程序編譯后的機(jī)器碼;這部分區(qū)域的大小在程序執(zhí)行前就已經(jīng)確定肠鲫,而且內(nèi)存區(qū)域?qū)儆谥蛔x员帮。
? ??????????????總結(jié):代碼段(Code)、只讀數(shù)據(jù)段(RO data)导饲、讀寫數(shù)據(jù)段(RW Data)捞高、未初始化數(shù)據(jù)段(BSS)屬于靜態(tài)區(qū)域。代碼段 和 已初始化的數(shù)據(jù)段(RO/RW data)?都在可運(yùn)行文件里渣锦,由系統(tǒng)從可運(yùn)行文件里載入硝岗;而BSS段不在可運(yùn)行文件里,由系統(tǒng)初始化袋毙。而堆和棧作為動(dòng)態(tài)區(qū)域,?在程序運(yùn)行的過(guò)程中分配和釋放型檀。(也就是說(shuō),一個(gè)可執(zhí)行程序分為映像和運(yùn)行兩種狀態(tài)娄猫。在編譯鏈接后形成的映像中贱除,將只包含代碼段(text)生闲、只讀數(shù)據(jù)段(RO data)和讀寫數(shù)據(jù)段(RW data)。在程序運(yùn)行之前加載的過(guò)程中月幌,將動(dòng)態(tài)生成未初始化數(shù)據(jù)段(BSS)碍讯,在程序運(yùn)行時(shí)將動(dòng)態(tài)生成堆(Heap)和棧(Stack)區(qū)域。)
? ??????3.?哈希表(也叫散列表)
? ??????????????哈希表的主干是數(shù)組扯躺。比如我們要新增或查找某個(gè)元素捉兴,可以使用哈希函數(shù)(這個(gè)哈希函數(shù)的設(shè)計(jì)直接影響到哈希表的優(yōu)劣)根據(jù)該元素的關(guān)鍵字直接映射到數(shù)組中的某個(gè)位置,通過(guò)這個(gè)位置下標(biāo)一次定位就可以完成操作录语。
? ??????????????哈希沖突:對(duì)兩個(gè)或多個(gè)元素進(jìn)行哈希計(jì)算得到同一個(gè)存儲(chǔ)地址倍啥。再好的函數(shù)也不能保證計(jì)算得到的內(nèi)存地址絕對(duì)不會(huì)發(fā)生沖突。哈希表采用了鏈地址法(鏈表)來(lái)解決哈希沖突澎埠。
? ??????????????哈希表的整體結(jié)構(gòu)如下:(該圖摘自某一文章虽缕,具體哪篇不記得了)
? ??????4. 垃圾回收
? ? ? ? ? ? ? ? GC?將內(nèi)存中的對(duì)象主要分成兩個(gè)區(qū)域:Young?區(qū)和Old?區(qū)。對(duì)象先在Young?區(qū)被創(chuàng)建蒲稳,然后如果經(jīng)過(guò)一段時(shí)間還存活著氮趋,則被移動(dòng)到Old?區(qū)。
? ??????????????Young?區(qū)的對(duì)象因?yàn)榇蟛糠稚诙己芏探看位厥罩笾挥猩俨糠帜軌虼婊钍P玻圆捎玫乃惴ń蠧opying?算法,簡(jiǎn)單說(shuō)來(lái)就是直接把活著的對(duì)象復(fù)制到另一個(gè)地方祥国。Young?區(qū)內(nèi)部又分成了三塊區(qū)域:Eden?區(qū), From?區(qū), To?區(qū)昵观。每次執(zhí)行Copying?算法時(shí),即將存活的對(duì)象從Eden?區(qū)和From?區(qū)復(fù)制到To?區(qū)舌稀,然后交換From?區(qū)和To?區(qū)的名字(即From?區(qū)變成To?區(qū)啊犬,To?區(qū)變成From?區(qū))。
? ??????????????Old?區(qū)的對(duì)象因?yàn)槎际谴婊钕聛?lái)的老司機(jī)了扩借,所以如果用Copying?算法的話椒惨,很可能90%?的對(duì)象都得復(fù)制一遍了,不劃算潮罪。所以O(shè)ld?區(qū)的回收算法叫Mark-Sweep?算法康谆。簡(jiǎn)單來(lái)說(shuō),就是只是把不用的對(duì)象先標(biāo)記(Mark)出來(lái)嫉到,然后回收(Sweep)沃暗,活著的對(duì)象就不動(dòng)它了。因?yàn)榇蟛糠謱?duì)象都活著何恶,所以回收下來(lái)的對(duì)象并不多孽锥。但是這個(gè)算法會(huì)有一個(gè)問(wèn)題:它會(huì)產(chǎn)生內(nèi)存碎片,所以它一般還會(huì)帶有整理內(nèi)存碎片的邏輯,在算法中叫做Compact惜辑,其實(shí)就是把對(duì)象插到這些空的位置里唬涧。
? ??????????????如何找出需要回收的垃圾對(duì)象?為了避免ARC?解決不了的循環(huán)引用問(wèn)題盛撑,GC?引入了一個(gè)叫做「可達(dá)性」的概念碎节,應(yīng)用這個(gè)概念,即使是有循環(huán)引用的垃圾對(duì)象抵卫,也可以被回收掉狮荔。當(dāng)GC?工作時(shí),GC?認(rèn)為當(dāng)前的一些對(duì)象是有效的介粘,這些對(duì)象包括:全局變量殖氏,棧里面的變量等,然后GC?從這些變量出發(fā)姻采,去標(biāo)記這些變量「可達(dá)」的其它變量雅采,這個(gè)標(biāo)記是一個(gè)遞歸的過(guò)程,最后就像從樹根的內(nèi)存對(duì)象開始偎谁,把所有的樹枝和樹葉都記成可達(dá)的了总滩。那除了這些「可達(dá)」的變量,別的變量就都需要被回收了巡雨。
IOS內(nèi)存管理機(jī)制
? ? ? ? 1.?MRC & ARC
????????????????MRC
????????????????????alloc:用來(lái)分配一個(gè)對(duì)象的內(nèi)存空間
? ??????????????????retain:引用計(jì)數(shù)加1
? ? ? ? ? ? ? ? ? ??release:引用計(jì)數(shù)減1
? ??????????????????retainCount:獲取當(dāng)前對(duì)象的引用計(jì)數(shù)值
? ? ? ? ? ? ? ? ? ??autorelease:當(dāng)前對(duì)象會(huì)在NSAutoreleasePool結(jié)束的時(shí)候調(diào)用它的release操作,進(jìn)行引用計(jì)數(shù)減1
? ??????????????????dealloc:顯式調(diào)用[super dealloc]
? ? ? ? ? ? ? ? ? ??copy:將對(duì)象拷貝為一個(gè)不可變對(duì)象
? ? ? ? ? ? ? ? ? ??mutablleCopy: ?拷貝出一個(gè)可變對(duì)象
? ? ? ? ? ? ? ? ARC:由LLVM和Runtime共同協(xié)作來(lái)進(jìn)行自動(dòng)引用計(jì)數(shù)的席函。
? ? ? ? ? ? ? ? ? ? 铐望。禁止手動(dòng)調(diào)用retain、release茂附、retainCount正蛙、autorelease,可以重寫某個(gè)對(duì)象的dealloc方法营曼,但不能顯式調(diào)用[super dealloc]乒验。
? ? ? ? ? ? ? ? ? ? 。使用@autoreleasepool?塊替代NSAutoreleasePool蒂阱。
? ? ? ? ? ? ? ? ? ? 锻全。同時(shí)使用ObjC指針和CFTypeRef指向的對(duì)象時(shí),由于CoreFoundation框架不支持ARC录煤,所以除了轉(zhuǎn)換類型鳄厌,還需指定內(nèi)存管理所有權(quán)的改變:
? ??????????????????????????__bridge?只聲明類型轉(zhuǎn)變,不做內(nèi)存管理規(guī)則的轉(zhuǎn)變妈踊。如以下例子了嚎,依然要用Objective-C?類型的ARC?來(lái)管理s1,你不能用CFRelease()?去釋放s1。
CFStringRef?s1?=?(__bridge?CFStringRef)?[[NSString?alloc]?initWithFormat:@"Hello,?%@!",?name];
? ???????????????????????????__bridge_retained(或CFBridgingRetain) 表示將指針類型轉(zhuǎn)變的同時(shí)歪泳,將內(nèi)存管理的責(zé)任由原來(lái)的Objective-C?交給Core Foundation?來(lái)處理萝勤,也就是,將ARC?轉(zhuǎn)變?yōu)镸RC呐伞。如下代碼纵刘,我們?cè)诘诙凶隽宿D(zhuǎn)化,這時(shí)內(nèi)存管理規(guī)則由ARC?變?yōu)榱薓RC荸哟,我們需要手動(dòng)的來(lái)管理s2?的內(nèi)存假哎,而對(duì)于s1,我們即使將其置為nil鞍历,也不能釋放內(nèi)存舵抹。
NSString?*s1?=?[[NSString?alloc]?initWithFormat:@"Hello,?%@!",?name];
CFStringRef?s2?=?(__bridge_retained?CFStringRef)s1;
?//?or?CFStringRef?s2?=?(CFStringRef)CFBridgingRetain(s1);
?//?do?something?with?s2
//...
CFRelease(s2);?//?注意要在使用結(jié)束后加這個(gè)
? ??????????????????????????__bridge_transfer(或CFBridgingRelease) 這個(gè)修飾符和函數(shù)的功能和上面那個(gè)__bridge_retained?相反,它表示將管理的責(zé)任由Core Foundation?轉(zhuǎn)交給Objective-C劣砍,即將管理方式由MRC?轉(zhuǎn)變?yōu)锳RC惧蛹。例如,這里我們將result?的管理責(zé)任交給了ARC?來(lái)處理刑枝,我們就不需要再顯式地將CFRelease()?了香嗓。
CFStringRef?result?=?CFURLCreateStringByAddingPercentEscapes(.?.?.);
NSString?*s?=?(__bridge_transfer?NSString?*)result;
//or?NSString?*s?=?(NSString?*)CFBridgingRelease(result);
return?s;
? ??????????????? ? 。ARC下新增的所有權(quán)修飾符:
? ??????????????????????????__strong?表示強(qiáng)引用装畅,對(duì)應(yīng)定義property時(shí)用strong靠娱。對(duì)象類型默認(rèn)都是strong。
? ??????????????????????? ? __weak?表示弱引用掠兄,對(duì)應(yīng)定義property時(shí)用weak像云。對(duì)象被釋放時(shí),所有指向它的弱引用都會(huì)被置為nil蚂夕,這樣可以防止野指針迅诬。因?yàn)閣eak不會(huì)引起對(duì)象的引用計(jì)數(shù)變化,因此婿牍,該對(duì)象在運(yùn)行過(guò)程中很有可能會(huì)被釋放侈贷。所以,需要將對(duì)象注冊(cè)到自動(dòng)釋放池中并在自動(dòng)釋放池銷毀時(shí)釋放對(duì)象占用的內(nèi)存等脂。如果大量使用weak的話俏蛮,在我們?nèi)ピL問(wèn)weak修飾的對(duì)象時(shí),會(huì)有大量對(duì)象注冊(cè)到自動(dòng)釋放池,這會(huì)影響程序的性能慎菲。推薦方案 : 要訪問(wèn)weak修飾的變量時(shí)嫁蛇,先將其賦給一個(gè)strong變量,然后進(jìn)行訪問(wèn)露该。
? ? ? ? ? ? ? ? ? ? ?睬棚。ARC相對(duì)于GC的優(yōu)點(diǎn)
? ??????????????????????????ARC?工作在編譯期,在運(yùn)行時(shí)沒有額外開銷。
? ??????????????????????????ARC?的內(nèi)存回收是平穩(wěn)進(jìn)行的抑党,對(duì)象不被使用時(shí)會(huì)立即被回收包警。而GC?的內(nèi)存回收是一陣一陣的,回收時(shí)需要暫停程序底靠,會(huì)有一定的卡頓害晦。
? ? ? ? ? ? ? ? ? ? ?。ARC相對(duì)于GC的缺點(diǎn):
? ? ? ? ? ? ? ? ? ? ? ?????GC?真的是太簡(jiǎn)單了暑中,基本上完全不用處理內(nèi)存管理問(wèn)題壹瘟,而ARC?還是需要處理類似循環(huán)引用這種內(nèi)存管理問(wèn)題。
? ?????????????????????????GC?一類的語(yǔ)言相對(duì)來(lái)說(shuō)學(xué)習(xí)起來(lái)更簡(jiǎn)單鳄逾。
? ???????2. ?TaggedPointer & NONPOINTER_ISA
????????????????32位機(jī)上指針占4個(gè)字節(jié)稻轨,所以一個(gè)對(duì)象包含兩個(gè)指針,占8個(gè)字節(jié)雕凹;64機(jī)上指針占8個(gè)字節(jié)殴俱,所以表示同一個(gè)對(duì)象需要16個(gè)字節(jié),翻倍了枚抵,其實(shí)本來(lái)8個(gè)字節(jié)就夠了线欲。因此,蘋果引入了Tagged Pointer和NONPOINTER_ISA來(lái)實(shí)現(xiàn)64位機(jī)上的內(nèi)存優(yōu)化汽摹。
? ??????????????TaggedPointer類型的對(duì)象李丰,它的isa指針為空,不會(huì)指向另一塊存儲(chǔ)空間竖慧,因?yàn)樗闹蛋谶@個(gè)指針本身里面了嫌套。對(duì)于一些小對(duì)象使用TaggedPointer這種內(nèi)存管理方案。比如NSNumber對(duì)象圾旨,11位長(zhǎng)度以下的NSString對(duì)象。
? ??????????????NONPOINTER_ISA在64位機(jī)上魏蔗,對(duì)象的isa區(qū)域不再只是一個(gè)指向另一塊存儲(chǔ)空間的指針砍的。還包含了更多信息,比如引用計(jì)數(shù)莺治,析構(gòu)狀態(tài)廓鞠,被其他weak?變量引用情況等。如果引用計(jì)數(shù)超過(guò)了當(dāng)前指針?biāo)鼙硎镜姆秶ヅ裕琑untime?會(huì)使用一張散列表來(lái)管理用計(jì)數(shù)床佳。
struct{//objc-private.h? ??????????????
????????uintptr_t nonpointer?: 1;
????????uintptr_t has_assoc?: 1;
????????uintptr_t has_cxx_dtor?: 1;
????????uintptr_t shiftcls?: 33;?// MACH_VM_MAX_ADDRESS 0x1000000000
????????uintptr_t magic?: 6;
????????uintptr_t weakly_referenced : 1;
????????uintptr_t deallocating?: 1;
????????uintptr_t has_sidetable_rc?: 1;
????????uintptr_t extra_rc?: 19;
?#?define RC_ONE?(1ULL<<45)
?#?define RC_HALF?(1ULL<<18)
?};
? ? ? ? ? ? ? ? 寫過(guò)一些例子來(lái)觀察IOS基本數(shù)據(jù)類型在內(nèi)存中的分布,總結(jié)如下:榄审?的地方我也沒有推測(cè)出是啥砌们,但基本不影響堆內(nèi)存分布的理解:
? ? ? ? ? ? ? ? 1)常量字符串:isa + ? +?內(nèi)容地址 (常量的isa不遵循以上nonpointer_isa規(guī)則,可能是因?yàn)槌A勘4嬖诔A繀^(qū),而不是堆區(qū)浪感,引用計(jì)數(shù)無(wú)窮大);copy后昔头,跟被copy對(duì)象一樣,同一個(gè)常量字符串影兽;mutableCopy后揭斧,變成一個(gè)分配了新地址的mutableString。
? ? ? ? ? ? ? ? 2)taggedPointerString:?直接存放內(nèi)容峻堰,0xa000000006362613(沒有isa)讹开;copy后,跟被copy對(duì)象一樣捐名,同一個(gè)taggedPointerString旦万;mutableCopy后,變成一個(gè)分配了新地址的mutableString桐筏。
? ? ? ? ? ? ? ? 3)NSString:isa + ?(似乎引用計(jì)數(shù)存放在該字節(jié)高32位里纸型,而不是isa里)+?內(nèi)容(內(nèi)容里最低字節(jié)存放字符長(zhǎng)度);copy后,跟被copy對(duì)象一樣梅忌;mutableCopy后狰腌,變成一個(gè)分配了新地址的mutableString。
? ? ? ? ? ? ? ? 4)mutableString: isa + ?(似乎引用計(jì)數(shù)存放在該字節(jié)高32位里牧氮,而不是isa里)+?內(nèi)容地址(內(nèi)容里最低字節(jié)存放字符長(zhǎng)度)琼腔;copy后沿癞,變成一個(gè)taggedPointerString(11位以下長(zhǎng)度)或NSString(11位以上長(zhǎng)度)奸鸯;mutableCopy后瀑梗,變成另外一個(gè)mutableString济似。appendString時(shí)极祸,可能會(huì)涉及擴(kuò)容問(wèn)題观谦,此時(shí)會(huì)分配新地址存放內(nèi)容顷窒,并銷毀舊地址谢床,同時(shí)它的存儲(chǔ)區(qū)的內(nèi)容地址會(huì)變成新分配的地址性含。
? ? ? ? ? ? ? ? 5)NSArray: isa + count +?內(nèi)容(這個(gè)內(nèi)容是數(shù)組里所有對(duì)象的地址的順序排列)洲赵;copy后,跟被copy的對(duì)象指向的是同一個(gè)地址商蕴;mutableCopy后叠萍,生成一個(gè)新的NSMutableArray,但其內(nèi)部所有對(duì)象的地址并未改變绪商。
? ? ? ? ? ? ? ? 6)NSMutableArray: isa + ? +?內(nèi)容地址(在內(nèi)容地址里存放的是數(shù)組里所有對(duì)象的地址的順序排列); copy后苛谷,變成了一個(gè)分配了新地址的NSArray,但其內(nèi)部所有對(duì)象的地址并未改變格郁;mutableCopy后腹殿,生成以一個(gè)新的堆地址來(lái)存放跟被拷貝對(duì)象一樣的內(nèi)容(isa + ? +?內(nèi)容地址)独悴,但是再給該mutableArray里添加對(duì)象的時(shí)候,其中存放的“isa +?赫蛇?+?內(nèi)容地址”會(huì)發(fā)生變化绵患。無(wú)論是被mutableCopy的對(duì)象,還是mutableCopy后的對(duì)象悟耘,在添加對(duì)象的時(shí)候落蝙,其內(nèi)容地址都有可能發(fā)生變化,這可能涉及到擴(kuò)容的問(wèn)題暂幼,添加對(duì)象時(shí)筏勒,系統(tǒng)開辟另一塊存儲(chǔ)空間,將之前的所有對(duì)象拷貝過(guò)去旺嬉,再添加新對(duì)象管行,同時(shí)銷毀之前的存儲(chǔ)空間。
? ? ? ? ? ? ? ? 7)SingleEntryDictionary: isa + value + key邪媳;copy后捐顷,跟被copy的對(duì)象指向的是同一個(gè)地址;mutableCopy后雨效,生成一個(gè)新的NSMutableDictionary迅涮,但其內(nèi)部所有key,value的地址并未改變徽龟。
? ? ? ? ? ? ? ? 8)NSDictionary: isa +?叮姑?(似乎低32位存放的是鍵值對(duì)個(gè)數(shù))+ key + value + key + value +…(三個(gè)以上個(gè)數(shù)似乎不是這樣的排列); copy后,跟被copy的對(duì)象指向的是同一個(gè)地址据悔;mutableCopy后传透,生成一個(gè)新的NSMutableDictionary,但其內(nèi)部所有key极颓,value的地址并未改變朱盐。
? ? ? ? ? ? ? ? 9)NSMutableDictionary: isa +?內(nèi)容地址(內(nèi)容里的鍵值對(duì)地址的排列規(guī)則還待進(jìn)一步驗(yàn)證) +??(似乎包含了鍵值對(duì)個(gè)數(shù))菠隆;copy后托享,重新分配了新的地址存放新的isa +?內(nèi)容地址(內(nèi)容地址跟被拷貝前的一樣);mutableCopy后浸赫,生成以一個(gè)新的堆地址來(lái)存放跟被拷貝對(duì)象一樣的內(nèi)容(isa +?內(nèi)容地址+??)赃绊,但是再給該mutableDictionary里添加鍵值對(duì)的時(shí)候既峡,其中存放的“isa +?內(nèi)容地址+??”會(huì)發(fā)生變化碧查。
? ??????3. ?引用計(jì)數(shù)表&?弱引用表
? ? ? ? ? ? ? ? SideTables包括了多個(gè)SideTable运敢,在不同系統(tǒng)架構(gòu)中SideTable的個(gè)數(shù)是不同的校仑;SideTables是哈希表,可以通過(guò)一個(gè)對(duì)象的指針來(lái)找到具體的引用計(jì)數(shù)表或弱引用表在哪一個(gè)具體的SideTable中传惠。
? ??????????????為什么用多個(gè)SideTable迄沫? 如果只有一個(gè)table,意味著內(nèi)存中分配的所有對(duì)象都要在一個(gè)表中操作卦方,因?yàn)槎鄠€(gè)線程可能同時(shí)操作這個(gè)表羊瘩,所以就要對(duì)這個(gè)表加鎖,如果并發(fā)操作這個(gè)表的線程有成千上萬(wàn)個(gè)盼砍,就會(huì)產(chǎn)生效率問(wèn)題尘吗。所以系統(tǒng)引入了分離鎖這樣一個(gè)技術(shù)方案,把大表拆成多個(gè)小表來(lái)進(jìn)行操作浇坐,分別對(duì)小表加鎖睬捶,從而提升效率。
? ??????????????自旋鎖:
? ??????????????????????自旋鎖:是“忙等”的鎖近刘。由于自旋時(shí)不釋放CPU擒贸,因而持有自旋鎖的線程應(yīng)該盡快釋放自旋鎖,否則其他等待該自旋鎖的線程會(huì)一直自旋觉渴,從而浪費(fèi)CPU時(shí)間介劫。
? ? ? ? ? ? ? ? ? ? ? ??自旋鎖適用于那些僅需要阻塞很短時(shí)間的場(chǎng)景。
? ? ? ? ? ? ? ? ? ? ? ??自旋鎖主要用來(lái)防止多處理器中并發(fā)訪問(wèn)臨界區(qū)疆拘,防止內(nèi)核搶占造成的競(jìng)爭(zhēng)蜕猫。如果持有自旋鎖的代碼sleep了就可能導(dǎo)致整個(gè)系統(tǒng)掛起。
? ??????????????????????自旋鎖與互斥鎖的比較:
? ??????????????????????????????使用任何鎖都需要消耗系統(tǒng)資源(內(nèi)存資源和CPU時(shí)間)哎迄,這種資源消耗可以分為兩類:
????????????????????????????????????????建立鎖所需要的資源
? ??????????????????????????????????????當(dāng)線程被阻塞時(shí)所需要的資源
? ??????????????????????????????對(duì)于自旋鎖來(lái)說(shuō)回右,它只需要消耗很少的資源來(lái)建立鎖;隨后當(dāng)線程被阻塞時(shí)漱挚,它就會(huì)一直重復(fù)檢查看鎖是否可用了翔烁,也就是說(shuō)當(dāng)自旋鎖處于等待狀態(tài)時(shí)它會(huì)一直消耗CPU時(shí)間。
? ??????????????????????????????而對(duì)于另一種常見的互斥鎖來(lái)說(shuō)旨涝,與自旋鎖相比它需要消耗大量的系統(tǒng)資源來(lái)建立鎖蹬屹;隨后當(dāng)線程被阻塞時(shí),線程的調(diào)度狀態(tài)被修改白华,并且線程被加入等待線程隊(duì)列慨默;最后當(dāng)鎖可用時(shí),在獲取鎖之前弧腥,線程會(huì)被從等待隊(duì)列取出并更改其調(diào)度狀態(tài)厦取;但是在線程被阻塞期間,它不消耗CPU資源管搪。
? ??????????????????????????????自旋鎖和互斥鎖適用于不同的場(chǎng)景虾攻≌÷颍互斥鎖適用于那些可能會(huì)阻塞很長(zhǎng)時(shí)間的場(chǎng)景。舉個(gè)例子:如果只有一行代碼需要加鎖霎箍,那么使用自旋鎖耗費(fèi)的時(shí)間會(huì)比互斥鎖少奇钞;而如果有一個(gè)需要跑10次的循環(huán)需要加鎖,那么互斥鎖應(yīng)該會(huì)是更好的選擇漂坏。
????????????????引用計(jì)數(shù)表:
? ??????????????????????alloc:?通過(guò)一系列函數(shù)調(diào)用景埃,最終直接調(diào)用calloc,不會(huì)進(jìn)行引用計(jì)數(shù)加1,見retainCount講解樊拓。//NSObject.mm alloc
? ??????????????????????retain:?判斷是否nonpointer纠亚,如果不是,直接去操作引用計(jì)數(shù)表筋夏,如果是蒂胞,先看isa引用計(jì)數(shù)位加1后是否越界,如果越界条篷,留下一半引用計(jì)數(shù)在isa的引用計(jì)數(shù)位骗随,其他復(fù)制到引用計(jì)數(shù)表去。//NSObject.mm retain
? ??????????????????????release:?與retain相反的過(guò)程赴叹。//NSObject.mm release
? ??????????????????????retainCount:?是nonpointer鸿染,isa引用計(jì)數(shù)位+引用計(jì)數(shù)表引用計(jì)數(shù)位+1;否則乞巧,引用計(jì)數(shù)表引用計(jì)數(shù)位+1涨椒。都要加1,所以說(shuō)alloc時(shí)引用計(jì)數(shù)并沒有加1绽媒。//NSObject.mm retainCount
? ?????????????????????dealloc: nonpointer_isa, weakly_referenced, has_assoc,has_cxx_dtor, has_sidetable_rc?這些都為false的時(shí)候蚕冬,才可以直接free,如果has_cxx_dtor是辕,需要destruct囤热,如果hasAssociatedObjects,需要移除關(guān)聯(lián)對(duì)象获三,不是nonpointer旁蔼,直接從弱引用表移除弱引用和擦除引用計(jì)數(shù)表里的引用計(jì)數(shù),是nonpointer疙教,需要再判斷weakly_referenced和has_sidetable_rc來(lái)決定要不要移除弱引用棺聊,要不要擦除引用計(jì)數(shù)表。//NSObject.mm dealloc
? ??????????????????????引用計(jì)數(shù)表贞谓,使用哈希表來(lái)實(shí)現(xiàn)躺屁,它的插入和獲取都是通過(guò)同一個(gè)哈希算法來(lái)得到一個(gè)size_t類型的值的,避免了for循環(huán)的使用经宏,從而保證了操作效率犀暑。哈希算法找到的位置,實(shí)際上是一個(gè)unsighed long?型的變量烁兰。
????????????????弱引用表:
? ??????????????????????__weak的對(duì)象通過(guò)調(diào)用initWeak和storeWeak來(lái)添加到弱引用表耐亏。//NSObject.mm storeWeak
? ??????????????????????如何將weak對(duì)象設(shè)為nil:dealloc -> weak_clear_no_lock(從弱引用表中取出所有該對(duì)象的weak_entry_t對(duì)象,weak_entry_t里存放了weak_referrer_t數(shù)組沪斟,就是所有的弱引用广辰,全部設(shè)為nil,然后再移除weak_entry_t對(duì)象)
? ??????????????????????弱引用表主之,也是一個(gè)哈希表择吊。對(duì)象指針通過(guò)一個(gè)哈希函數(shù)的運(yùn)算,得到一個(gè)weak_entry_t槽奕,weak_entry_t?的結(jié)構(gòu)和weak_table_t有一點(diǎn)點(diǎn)類似几睛,基本也是一個(gè)HashTable的實(shí)現(xiàn)。
? ??????????????????????weak_entry_t有一個(gè)巧妙的設(shè)計(jì)粤攒,即如果一個(gè)對(duì)象對(duì)應(yīng)的弱引用數(shù)目較少的話(<=WEAK_INLINE_COUNT所森,runtime把這個(gè)值設(shè)置為4),則其弱引用會(huì)被依次保存到一個(gè)inline數(shù)組里夯接。這個(gè)inline數(shù)組的內(nèi)存會(huì)在weak_entry_t初始化的時(shí)候一并分配好焕济,而不是需要用到的時(shí)候再去申請(qǐng)新的內(nèi)存空間,從而達(dá)到提到運(yùn)行效率的目的盔几。
????????????????代碼實(shí)現(xiàn):
staticStripedMap<SideTable>& SideTables() {//NSObject.mm
??return*reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
?template<typenameT>//objc-private.h
?classStripedMap {
??enum{ CacheLineSize = 64 };
?#if TARGET_OS_EMBEDDED
??enum{ StripeCount = 8 };
?#else
??enum{ StripeCount = 64 };
#endif
?structPaddedT {
????????T value alignas(CacheLineSize);
??};
??PaddedTarray[StripeCount];
??staticunsignedintindexForPointer(constvoid*p) {
????????uintptr_taddr =?reinterpret_cast<uintptr_t>(p);
????????return((addr >> 4) ^ (addr >> 9)) %?StripeCount;
?}
?…}
?structSideTable {
??spinlock_tslock;
??RefcountMaprefcnts;
??weak_table_tweak_table;
?…}
typedefobjc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
?structweak_table_t {//objc-weak.h
??weak_entry_t*weak_entries;
??size_t?num_entries;
??uintptr_tmask;
??uintptr_tmax_hash_displacement;
};
?structweak_entry_t {
??DisguisedPtr<objc_object> referent;
??union{
????????struct{
????????????weak_referrer_t*referrers;
????????????uintptr_t?out_of_line_ness : 2;
????????????uintptr_t?num_refs :?PTR_MINUS_2;
????????????uintptr_t?mask;
????????????uintptr_t?max_hash_displacement;
????????};
????????struct{
????????????// out_of_line_ness field is low bits of inline_referrers[1]
????????????weak_referrer_t?inline_referrers[WEAK_INLINE_COUNT];
????????};
??};
??boolout_of_line() {
???????return(out_of_line_ness ==?REFERRERS_OUT_OF_LINE);
??}
??weak_entry_t&operator=(constweak_entry_t& other) {
????????memcpy(this, &other,?sizeof(other));
????????return*this;
??}
??weak_entry_t(objc_object*newReferent,objc_object**newReferrer)
???????: referent(newReferent)
??{
????????inline_referrers[0] = newReferrer;
????????for(inti = 1; i <?WEAK_INLINE_COUNT; i++) {
????????????inline_referrers[i] =?nil;
????????}
??}
};
typedefDisguisedPtr<objc_object*> weak_referrer_t;
? ??????4. ?自動(dòng)釋放池
????????????????Main函數(shù)自動(dòng)添加了@autoreleasepool{};?很多次循環(huán)的內(nèi)部最好自己添加@autoreleasepool{},以及時(shí)釋放其內(nèi)部的臨時(shí)對(duì)象晴弃。? ??????????????
? ??????????????實(shí)現(xiàn)原理:?以棧為節(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成的。
? ??????????????????????objc_autoreleasePoolPush (插入哨兵對(duì)象)
? ??????????????????????objc_autoreleasePoolPop (批量的釋放操作)
? ? ? ? ? ? ? ? 總結(jié):
? ??????????????????????在當(dāng)次runloop將要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::pop()逊拍。
? ??????????????????????多層嵌套就是多次插入哨兵對(duì)象上鞠。
? ??????????????????????在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(dòng)插入autoreleasePool。
循環(huán)引用
? ? ? ?分類
? ??????????????自循環(huán)引用:如block
? ??????????????相互循環(huán)引用:如delegate
? ??????????????多循環(huán)引用
? ??????如何破除循環(huán)引用
? ??????????????使用__weak所有權(quán)修飾符
? ? ? ? ? ? ? ??使用__block
? ??????????????????????MRC下顺献,__block修飾對(duì)象不會(huì)增加其引用計(jì)數(shù)旗国,避免了循環(huán)引用。
? ??????????????????????ARC下注整,__block修飾對(duì)象會(huì)被強(qiáng)引用能曾,無(wú)法避免循環(huán)引用,需手動(dòng)解環(huán)肿轨。
? ??????????????使用__unsafe_unretained
? ??????????????????????修飾對(duì)象不會(huì)增加其引用計(jì)數(shù)寿冕,避免了循環(huán)引用。
? ??????????????????????如果被修飾對(duì)象在某一時(shí)機(jī)被釋放椒袍,會(huì)產(chǎn)生懸垂指針(導(dǎo)致內(nèi)存泄漏)驼唱。
? ??????循環(huán)引用示例
? ??????????????NSTimer
? ??????????????delegate
? ??????????????block
? ??????????????????????_NSConcreteGlobalBlock
? ??????????????????????????????存儲(chǔ)在已初始化數(shù)據(jù)區(qū);?
????????????????????????????????Copy后驹暑,什么也不做
? ??????????????????????_NSConcreteStackBlock
? ??????????????????????????????存儲(chǔ)在棧區(qū)玫恳,作用域結(jié)束會(huì)被銷毀辨赐;
?????????????????????????????????Copy后,存放到堆上京办,作用域結(jié)束后棧上的block會(huì)被銷毀掀序,堆上的仍然存在
? ??????????????????????_NSConcreteMallocBlock
? ??????????????????????????????存儲(chǔ)在堆區(qū)
? ??????????????????????????????Copy后,增加引用計(jì)數(shù)