內(nèi)存布局
-
- 內(nèi)存區(qū)域: 內(nèi)核區(qū)(low) - 程序加載 - 保留(high)
- 程序:
- 未初始化數(shù)據(jù)(.bss)
- 已初始化數(shù)據(jù)(.data)
- 代碼段(.text)
- 棧(stack) : 方法函數(shù)(high->low), 方法的調(diào)用
- 堆(heap) : 對(duì)象和block(low->high), 通過alloc分配的對(duì)象
-
內(nèi)存布局
- bss: 未初始化的全局變量等
- data: 已初始化的全局變量等
- text: 程序代碼
內(nèi)存管理方案
iOS如果對(duì)內(nèi)存進(jìn)行管理?針對(duì)不同場(chǎng)景, 進(jìn)行不同的管理
- 小對(duì)象: TaggedPointer(NSNumber等)
-
- 64位對(duì)象: NONPOINTER_ISA(非指針型isa)
- 第1位: 0-> 指針型isa; 1-> 非指針型
- 第2位: has_assoc當(dāng)前對(duì)象是否有關(guān)聯(lián)對(duì)象(0: 無, 1: 有)
- 第3位: has_cxx_dtor當(dāng)前對(duì)象是否有使用到C++語言或者代碼
- 第4-36位: 保存當(dāng)前對(duì)象的類對(duì)象的指針地址
- 第37-42位: magic
- 第43位: waekly_referenced標(biāo)識(shí)當(dāng)前對(duì)象是否有弱引用指針
- 第44位: deallocating當(dāng)前對(duì)象是否正在做dealloc操作
- 第45位: has_sidetable_rc當(dāng)前isa指針中所存儲(chǔ)的引用計(jì)數(shù)已經(jīng)達(dá)到上限的話
- 第45-64位:extra_rc額外的引用計(jì)數(shù)
-
- 散列表: 是一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu), 其中包含了引用計(jì)數(shù)表和弱引用表
- SideTables()結(jié)構(gòu):
- SideTables
-
- Side Table:
- Side Table
- 為什么不是一個(gè)Side Table?
- 如果只有一個(gè)SideTable, 那么在不同線程的所有的數(shù)據(jù)都會(huì)訪問這個(gè)表, 因此改表需要加鎖, 而當(dāng)所有數(shù)據(jù)都要訪問的時(shí)候, 此時(shí)就會(huì)產(chǎn)生效率的問題, 所以此時(shí)系統(tǒng)引入了分離鎖
- 分離鎖: 拆分為多個(gè)SideTable, 提高訪問效率
- 怎樣實(shí)現(xiàn)快速分流?(我通過一個(gè)對(duì)象的指針, 如何快速的定位到它屬于sideTables中的哪一張表)
- SideTables的本質(zhì)是一張Hash表
- Hash查找
- Hash查找的過程:
- 哈希取余查找, 提高查找效率 - 防止遍歷
- SideTables的本質(zhì)是一張Hash表
數(shù)據(jù)結(jié)構(gòu)
-
- 自旋鎖(Spinlock_t)
- 忙等的鎖, 如果當(dāng)前鎖, 已經(jīng)被其他線程獲取, 那么當(dāng)前現(xiàn)場(chǎng)會(huì)不斷探測(cè)該鎖有沒有被釋放, 如果釋放了, 第一時(shí)間獲取該鎖
- 適用于輕量訪問
- 是否使用過自旋鎖, 自旋鎖與普通鎖有何區(qū)別, 自旋鎖的使用場(chǎng)景?
- !!!
- 自旋鎖與普通鎖有何區(qū)別?
- 正常的信號(hào)量 : 當(dāng)獲取不到鎖的時(shí)候, 會(huì)把自己的線程阻塞休眠; 當(dāng)其他線程用完的時(shí)候喚醒線程
-
- 引用計(jì)數(shù)表(RefcountMap)
- hash查找: (插入和獲取是通過同一個(gè)hash函數(shù)決定的, 避免了for循環(huán)遍歷, 提高效率)
- 屏幕快照 2019-02-18 下午7.37.07.png
- RC引用計(jì)數(shù)值
-
- 弱引用表(weak_table_t)
- 弱引用表的數(shù)據(jù)結(jié)構(gòu)
ARC & MRC
- MRC: 手動(dòng)引用計(jì)數(shù)
- MRC(紅色為MRC特有方法)
- ARC: 自動(dòng)引用計(jì)數(shù)
- 什么是ARC? ARC是LLVM和Runtime協(xié)作的結(jié)果
- ARC中禁止手動(dòng)調(diào)用retain, release, retainCount, dealloc
- ARC中新增weak, strong屬性關(guān)鍵字
- weak變量為什么在自動(dòng)釋放的時(shí)候指向nil
引用計(jì)數(shù)管理
-
- alloc:
- 經(jīng)過一系列調(diào)用, 最終調(diào)用了C函數(shù)calloc,
- 此時(shí)并沒有應(yīng)用計(jì)數(shù)+1(因?yàn)橄旅娴膔efcnt-result=1, 所以調(diào)用alloc的時(shí)候retainCount是1)
-
- retain:
- SIDE_TABLE_RC_ONE對(duì)應(yīng)的偏移量
- 我們?cè)谶M(jìn)行retain的時(shí)候, 系統(tǒng)是如何查找其對(duì)應(yīng)的應(yīng)用計(jì)數(shù)的?
- 經(jīng)過2次的hash查找, 找到對(duì)應(yīng)的引用計(jì)數(shù)的值, 然后在進(jìn)行相應(yīng)的+1操作
-
- release實(shí)現(xiàn)
- SIDE_TABLE_RC_ONE
-
- retainCount實(shí)現(xiàn)
- alloc的時(shí)候it為空的, 因?yàn)閞efcnt_result = 1, 所以alloc的retainCount為1
-
- dealloc實(shí)現(xiàn)
-
實(shí)現(xiàn)原理
- 是否可以釋放的判斷標(biāo)準(zhǔn): (以下都為no, 則C函數(shù)free())
-
nonpointer_isa: 判斷當(dāng)前對(duì)象是否使用了非指針型的isa, (非關(guān)系型會(huì)存儲(chǔ)引用計(jì)數(shù), 超過了則會(huì)使用has_sidetable_rc) -
weakly_referenced: 判斷當(dāng)前對(duì)象是否有weak指針指向它 -
has_assoc: 判斷當(dāng)前對(duì)象是否有關(guān)聯(lián)對(duì)象 -
has_cxx_dtor: 判斷當(dāng)前對(duì)象內(nèi)部實(shí)現(xiàn)是否涉及到C++, 當(dāng)前對(duì)象是否使用ARC管理內(nèi)存; 若有即為YES -
has_sidetable_rc: 判斷當(dāng)前對(duì)象的引用計(jì)數(shù)是否通過sidetable的引用計(jì)數(shù)表來維護(hù)的
-
object_dispose實(shí)現(xiàn)():
-
object_dispose流程 - objc_destructInstance()實(shí)現(xiàn):
- objc_destructInstance流程
- 通過關(guān)聯(lián)對(duì)象的技術(shù), 為一個(gè)類添加了一些實(shí)例變量, 在對(duì)象dealloc方法中, 是否有必要對(duì)其關(guān)聯(lián)對(duì)象進(jìn)行移除呢? (不需要, 系統(tǒng)會(huì)自動(dòng)幫助我們移除的)
- clearDeallocating()實(shí)現(xiàn):
- clearDeallocating內(nèi)部實(shí)現(xiàn)
弱引用管理
-
- 一個(gè)weak變量是如何添加到弱引用表中的?
-
一個(gè)__weak修飾的變量, 編譯之后的樣子
- 添加weak變量流程
-
- weak變量是如何添加到弱引用表中 : 通過弱引用對(duì)象進(jìn)行hash算法的計(jì)算, 然后計(jì)算查找期所對(duì)應(yīng)的位置
-
源碼實(shí)現(xiàn)
id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak<false/*old*/, true/*new*/, true/*crash*/> (location, (objc_object*)newObj); }storeWeak(id *location, objc_object *newObj) { assert(HaveOld || HaveNew); if (!HaveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (HaveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (HaveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable); if (HaveOld && *location != oldObj) { SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. if (HaveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. if (HaveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. if (HaveNew) { // 向弱引用表中, 插入對(duì)象 newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable); return (id)newObj; }weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; if (!referent || referent->isTaggedPointer()) return referent_id; // ensure that the referenced object is viable bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else { return nil; } } // now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { weak_entry_t new_entry; new_entry.referent = referent; new_entry.out_of_line = 0; new_entry.inline_referrers[0] = referrer; for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) { new_entry.inline_referrers[i] = nil; } weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; }weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { assert(referent); weak_entry_t *weak_entries = weak_table->weak_entries; if (!weak_entries) return nil; size_t index = hash_pointer(referent) & weak_table->mask; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent != referent) { index = (index+1) & weak_table->mask; hash_displacement++; if (hash_displacement > weak_table->max_hash_displacement) { return nil; } } return &weak_table->weak_entries[index]; }
-
- 當(dāng)一個(gè)weak變量被釋放或者廢棄之后, 系統(tǒng)是如何處理的?
- 清除weak變量, 同時(shí)設(shè)置指向?yàn)閚il:
-
清除weak流程
- 源碼分析:
*referrer = nil;weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); }
- 源碼分析:
-
自動(dòng)釋放池
-
- 自動(dòng)釋放池內(nèi)部實(shí)現(xiàn):
- 編譯器將@autoreleasepool{}修改為上圖
-
- objc_autoreleasePoolPush:
- objc_autoreleasePoolPush內(nèi)部流程
-
- objc_autoreleasePoolPop
-
屏幕快照 2019-02-18 下午8.21.21.png
- 如何理解一次pop相當(dāng)于一次批量的pop操作:
{}代碼中所有的數(shù)據(jù), 都會(huì)被pop一次
- 如何理解一次pop相當(dāng)于一次批量的pop操作:
-
- AutoreleasePool的實(shí)現(xiàn)結(jié)構(gòu)是怎么樣的 / 什么是自動(dòng)釋放池?
- 以棧為節(jié)點(diǎn), 通過雙向鏈表的形式組合而成的數(shù)據(jù)結(jié)構(gòu)
- 雙向鏈表
- 棧節(jié)點(diǎn)
- 是與線程一一對(duì)應(yīng)的
-
- AutoreleasePoolPage :
-
AutoreleasePoolPage內(nèi)部結(jié)構(gòu)
-
id * next: 指向棧當(dāng)中下一個(gè)可填充的位置 -
AutoreleasePoolPage * const parent: 雙向鏈表中的父指針 -
AutoreleasePoolPage * child: 雙向鏈表中的子指針 -
pthread_t const thread;: 線程對(duì)象, 所以說autoreleasePool是與線程一一對(duì)應(yīng)的
-
- AutoreleasePoolPage的棧內(nèi)存: 棧底保存自身占用的內(nèi)存
- AutoreleasePoolPage的占內(nèi)存
-
- AutoreleasePoolPage::push:
-
AutoreleasePoolPage::push
- 1> 將當(dāng)前next指針指向nil
- 2> 然后next指針指向下一個(gè)可入棧的內(nèi)存地址(每次push的時(shí)候, 相當(dāng)于不斷的插入哨兵對(duì)象)
-
- [obj autorelease] :
- autorelease流程
- 棧內(nèi)存的流程
-
- AutoreleasePoolPage::pop :
- 根據(jù)傳入的哨兵對(duì)象找到對(duì)應(yīng)位置
- 給上次push操作之后添加的對(duì)象一次發(fā)送release消息
- 回退next指針到正確位置
- 原始
- 處理之后
-
- 關(guān)于自動(dòng)釋放池的面試
-
- viewDidLoad中創(chuàng)建一個(gè)array, 什么時(shí)候釋放?
-
array何時(shí)釋放呢?
- 在每一次runloop的循環(huán)過程當(dāng)中, 都會(huì)再其將要結(jié)束的時(shí)候, 對(duì)前一次創(chuàng)建的pool進(jìn)行pop操作, 同時(shí)push進(jìn)來新的pool; 所以創(chuàng)建的arr對(duì)象, 在當(dāng)次runloop將要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::pop(), 讓arr對(duì)象調(diào)用release方法, 進(jìn)行釋放
-
- AutoreleasePool中為何可以嵌套使用?
- 多層嵌套就是多次插入哨兵對(duì)象
- autoreleasePool的使用場(chǎng)景: 在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(dòng)插入autoreleasePool
- 自動(dòng)釋放池的雙向鏈表應(yīng)用體現(xiàn)在哪里?
循環(huán)引用
-
- 循環(huán)應(yīng)用的分類:
- 自循環(huán)引用:
- 相互循環(huán)應(yīng)用:
- 自循環(huán)引用
- 多循環(huán)引用:
- 大環(huán)多循環(huán)引用
-
- 循環(huán)引用的場(chǎng)景
- 代理
- block
- NSTimer
- 大環(huán)引用
-
- 如何破除循環(huán)引用?
- 避免產(chǎn)生循環(huán)應(yīng)用
- 在合適的時(shí)機(jī)手動(dòng)斷環(huán)
-
- 具體的解決方案?
- __weak:
- __weak破解
- __block:
- 在ARC下, __block修飾的對(duì)象會(huì)被強(qiáng)引用, 無法避免循環(huán)應(yīng)用, 需要手動(dòng)破環(huán)
- 在MRC下, __block修飾的對(duì)象不會(huì)增加其應(yīng)用計(jì)數(shù), 避免了循環(huán)引用
- __unsafe__unretained : 與__weak在效果上是等效的
- 修飾對(duì)象不會(huì)增加其應(yīng)用計(jì)數(shù), 避免了循環(huán)引用
- 如果被修飾對(duì)象在某一時(shí)機(jī)被釋放, 會(huì)產(chǎn)生懸垂指針, 導(dǎo)致內(nèi)存泄漏!
-
- 循環(huán)引用NSTimer示例:
- 場(chǎng)景: 頁(yè)面中有一個(gè)廣告欄, 定時(shí)滾動(dòng)播放動(dòng)畫, VC強(qiáng)引用廣告欄, 廣告欄添加NSTimer, NSTimer會(huì)對(duì)Target進(jìn)行強(qiáng)引用(即VC), 此時(shí)就會(huì)產(chǎn)生了循環(huán)應(yīng)用; 若把對(duì)象弱引用NSTimer也無用, 因?yàn)镹STimer會(huì)被當(dāng)前的RunLoop進(jìn)行強(qiáng)引用, NSTimer對(duì)對(duì)象的強(qiáng)引用, 當(dāng)VC退出的時(shí)候, 對(duì)象和NSTimer被RunLoop持有, 也不會(huì)被釋放
- 屏幕快照 2019-02-18 下午8.46.42.png
- NSTimer分為重復(fù)定時(shí)器和非重復(fù)定時(shí)器
- 重復(fù)定時(shí)器: 添加一個(gè)中間對(duì)象, RunLoop --> NSTimer <-=> 中間對(duì)象 ->對(duì)象 <--VC
- 添加中間對(duì)象, 利用了一個(gè)對(duì)象被釋放的時(shí)候, 其指針會(huì)置為nil
- 代碼實(shí)現(xiàn):
#import "NSTimer+WeakTimer.h" @interface TimerWeakObject : NSObject @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL selector; @property (nonatomic, weak) NSTimer *timer; - (void)fire:(NSTimer *)timer; @end @implementation TimerWeakObject - (void)fire:(NSTimer *)timer { if (self.target) { if ([self.target respondsToSelector:self.selector]) { [self.target performSelector:self.selector withObject:timer.userInfo]; } } else{ [self.timer invalidate]; } } @end @implementation NSTimer (WeakTimer) + (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats { TimerWeakObject *object = [[TimerWeakObject alloc] init]; object.target = aTarget; object.selector = aSelector; object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats]; return object.timer; } @end
- 非重復(fù)定時(shí)器: 在定時(shí)器的回調(diào)方法中調(diào)用invalid方法, 使timer置為nil, 即可以解除Runloop對(duì)timer的強(qiáng)引用, 同時(shí)也可以解除timer對(duì)對(duì)象的強(qiáng)應(yīng)用, 此時(shí)即可解除循環(huán)引用
- 重復(fù)定時(shí)器: 添加一個(gè)中間對(duì)象, RunLoop --> NSTimer <-=> 中間對(duì)象 ->對(duì)象 <--VC
內(nèi)存管理面試總結(jié):
- 什么是ARC?
- ARC是由LLVM編譯器和Runtime共同協(xié)作來實(shí)現(xiàn)對(duì)自動(dòng)引用計(jì)數(shù)的管理
- 為什么weak指針指向的對(duì)象那個(gè)在廢棄之后會(huì)被自動(dòng)置為nil?
- 當(dāng)對(duì)象被廢棄之后, dealloc方法的內(nèi)部實(shí)現(xiàn)當(dāng)中, 會(huì)調(diào)用清除弱引用的方法, 在清除弱引用的方法中, 會(huì)通過hash算法, 來查找被廢棄對(duì)象在弱引用表中的位置, 來提取其對(duì)應(yīng)弱引用指針的列表數(shù)組, 然后進(jìn)行for循環(huán)遍歷, 把每一個(gè)weak指針都置為nil
- 蘋果是如何實(shí)現(xiàn)AutoreleasePool的?
- AutoReleasePool是以棧為結(jié)點(diǎn), 由雙向鏈表形式來合成的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)的
- 什么是循環(huán)引用? 你遇到過哪些循環(huán)引用, 是怎樣解決的?




































