内存布局
-
- 内存区域: 内核区(low) - 程序加载 - 保留(high)
- 程序:
- 未初始化数据(.bss)
- 已初始化数据(.data)
- 代码段(.text)
- 栈(stack) : 方法函数(high->low), 方法的调用
- 堆(heap) : 对象和block(low->high), 通过alloc分配的对象
-
- bss: 未初始化的全局变量等
- data: 已初始化的全局变量等
- text: 程序代码
内存管理方案
iOS如果对内存进行管理?
针对不同场景, 进行不同的管理
- 小对象: TaggedPointer(NSNumber等)
-
- 64位对象: NONPOINTER_ISA(非指针型isa)
- 第1位: 0-> 指针型isa; 1-> 非指针型
- 第2位: has_assoc当前对象是否有关联对象(0: 无, 1: 有)
- 第3位: has_cxx_dtor当前对象是否有使用到C++语言或者代码
- 第4-36位: 保存当前对象的类对象的指针地址
- 第37-42位: magic
- 第43位: waekly_referenced标识当前对象是否有弱引用指针
- 第44位: deallocating当前对象是否正在做dealloc操作
- 第45位: has_sidetable_rc当前isa指针中所存储的引用计数已经达到上限的话
- 第45-64位:extra_rc额外的引用计数
-
- 散列表: 是一个复杂的数据结构, 其中包含了引用计数表和弱引用表
- SideTables()结构:
-
- Side Table:
- 为什么不是一个Side Table?
- 如果只有一个SideTable, 那么在不同线程的所有的数据都会访问这个表, 因此改表需要加锁, 而当所有数据都要访问的时候, 此时就会产生效率的问题, 所以此时系统引入了分离锁
- 分离锁: 拆分为多个SideTable, 提高访问效率
- 怎样实现快速分流?(我通过一个对象的指针, 如何快速的定位到它属于sideTables中的哪一张表)
- SideTables的本质是一张Hash表
- Hash查找的过程:
- SideTables的本质是一张Hash表
数据结构
-
- 自旋锁(Spinlock_t)
- 忙等的锁, 如果当前锁, 已经被其他线程获取, 那么当前现场会不断探测该锁有没有被释放, 如果释放了, 第一时间获取该锁
- 适用于轻量访问
- 是否使用过自旋锁, 自旋锁与普通锁有何区别, 自旋锁的使用场景?
- !!!
- 自旋锁与普通锁有何区别?
- 正常的信号量 : 当获取不到锁的时候, 会把自己的线程阻塞休眠; 当其他线程用完的时候唤醒线程
-
- 引用计数表(RefcountMap)
- hash查找: (插入和获取是通过同一个hash函数决定的, 避免了for循环遍历, 提高效率)
-
- 弱引用表(weak_table_t)
ARC & MRC
- MRC: 手动引用计数
- ARC: 自动引用计数
- 什么是ARC? ARC是LLVM和Runtime协作的结果
- ARC中禁止手动调用retain, release, retainCount, dealloc
- ARC中新增weak, strong属性关键字
- weak变量为什么在自动释放的时候指向nil
引用计数管理
-
- alloc:
- 经过一系列调用, 最终调用了C函数calloc,
- 此时并没有应用计数+1(因为下面的refcnt-result=1, 所以调用alloc的时候retainCount是1)
-
- retain:
- 我们在进行retain的时候, 系统是如何查找其对应的应用计数的?
- 经过2次的hash查找, 找到对应的引用计数的值, 然后在进行相应的+1操作
-
- release实现
-
- retainCount实现
-
- dealloc实现
-
- 是否可以释放的判断标准: (以下都为no, 则C函数free())
-
nonpointer_isa
: 判断当前对象是否使用了非指针型的isa, (非关系型会存储引用计数, 超过了则会使用has_sidetable_rc) -
weakly_referenced
: 判断当前对象是否有weak指针指向它 -
has_assoc
: 判断当前对象是否有关联对象 -
has_cxx_dtor
: 判断当前对象内部实现是否涉及到C++, 当前对象是否使用ARC管理内存; 若有即为YES -
has_sidetable_rc
: 判断当前对象的引用计数是否通过sidetable的引用计数表来维护的
-
object_dispose实现():
-
- objc_destructInstance()实现:
- 通过关联对象的技术, 为一个类添加了一些实例变量, 在对象dealloc方法中, 是否有必要对其关联对象进行移除呢? (不需要, 系统会自动帮助我们移除的)
- clearDeallocating()实现:
弱引用管理
-
- 一个weak变量是如何添加到弱引用表中的?
-
- weak变量是如何添加到弱引用表中 : 通过弱引用对象进行hash算法的计算, 然后计算查找期所对应的位置
-
源码实现
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) { // 向弱引用表中, 插入对象 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]; }
-
- 当一个weak变量被释放或者废弃之后, 系统是如何处理的?
- 清除weak变量, 同时设置指向为nil:
-
- 源码分析:
*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); }
- 源码分析:
-
自动释放池
-
- 自动释放池内部实现:
-
- objc_autoreleasePoolPush:
-
- objc_autoreleasePoolPop
-
- 如何理解一次pop相当于一次批量的pop操作:
{}代码
中所有的数据, 都会被pop一次
- 如何理解一次pop相当于一次批量的pop操作:
-
- AutoreleasePool的实现结构是怎么样的 / 什么是自动释放池?
- 以栈为节点, 通过双向链表的形式组合而成的数据结构
- 是与线程一一对应的
-
- AutoreleasePoolPage :
-
-
id * next
: 指向栈当中下一个可填充的位置 -
AutoreleasePoolPage * const parent
: 双向链表中的父指针 -
AutoreleasePoolPage * child
: 双向链表中的子指针 -
pthread_t const thread;
: 线程对象, 所以说autoreleasePool是与线程一一对应的
-
- AutoreleasePoolPage的栈内存: 栈底保存自身占用的内存
-
- AutoreleasePoolPage::push:
-
- 1> 将当前next指针指向nil
- 2> 然后next指针指向下一个可入栈的内存地址(每次push的时候, 相当于不断的插入哨兵对象)
-
- [obj autorelease] :
-
- AutoreleasePoolPage::pop :
- 根据传入的哨兵对象找到对应位置
- 给上次push操作之后添加的对象一次发送release消息
- 回退next指针到正确位置
-
- 关于自动释放池的面试
-
- viewDidLoad中创建一个array, 什么时候释放?
-
- 在每一次runloop的循环过程当中, 都会再其将要结束的时候, 对前一次创建的pool进行pop操作, 同时push进来新的pool; 所以创建的arr对象, 在当次runloop将要结束的时候调用AutoreleasePoolPage::pop(), 让arr对象调用release方法, 进行释放
-
- AutoreleasePool中为何可以嵌套使用?
- 多层嵌套就是多次插入哨兵对象
- autoreleasePool的使用场景: 在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool
- 自动释放池的双向链表应用体现在哪里?
循环引用
-
- 循环应用的分类:
- 自循环引用:
- 相互循环应用:
- 多循环引用:
-
- 循环引用的场景
- 代理
- block
- NSTimer
- 大环引用
-
- 如何破除循环引用?
- 避免产生循环应用
- 在合适的时机手动断环
-
- 具体的解决方案?
- __weak:
- __block:
- 在ARC下, __block修饰的对象会被强引用, 无法避免循环应用, 需要手动破环
- 在MRC下, __block修饰的对象不会增加其应用计数, 避免了循环引用
- __unsafe__unretained : 与__weak在效果上是等效的
- 修饰对象不会增加其应用计数, 避免了循环引用
- 如果被修饰对象在某一时机被释放, 会产生悬垂指针, 导致内存泄漏!
-
- 循环引用NSTimer示例:
- 场景: 页面中有一个广告栏, 定时滚动播放动画, VC强引用广告栏, 广告栏添加NSTimer, NSTimer会对Target进行强引用(即VC), 此时就会产生了循环应用; 若把对象弱引用NSTimer也无用, 因为NSTimer会被当前的RunLoop进行强引用, NSTimer对对象的强引用, 当VC退出的时候, 对象和NSTimer被RunLoop持有, 也不会被释放
- NSTimer分为重复定时器和非重复定时器
- 重复定时器: 添加一个中间对象, RunLoop --> NSTimer <-=> 中间对象 ->对象 <--VC
- 代码实现:
#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
- 非重复定时器: 在定时器的回调方法中调用invalid方法, 使timer置为nil, 即可以解除Runloop对timer的强引用, 同时也可以解除timer对对象的强应用, 此时即可解除循环引用
- 重复定时器: 添加一个中间对象, RunLoop --> NSTimer <-=> 中间对象 ->对象 <--VC
内存管理面试总结:
- 什么是ARC?
- ARC是由LLVM编译器和Runtime共同协作来实现对自动引用计数的管理
- 为什么weak指针指向的对象那个在废弃之后会被自动置为nil?
- 当对象被废弃之后, dealloc方法的内部实现当中, 会调用清除弱引用的方法, 在清除弱引用的方法中, 会通过hash算法, 来查找被废弃对象在弱引用表中的位置, 来提取其对应弱引用指针的列表数组, 然后进行for循环遍历, 把每一个weak指针都置为nil
- 苹果是如何实现AutoreleasePool的?
- AutoReleasePool是以栈为结点, 由双向链表形式来合成的数据结构来实现的
- 什么是循环引用? 你遇到过哪些循环引用, 是怎样解决的?