iOS 内存布局&内存管理
1. 内存布局
-
stack(栈区)
: 方法调用, 地址从高到低; -
heap(堆区)
:通过alloc
创建的对象和block
被copy
都放在这里, 地址从低到高; -
bss
:未初始化的全局变量等; -
data
:已初始化的全局变量等; -
text
:程序中的代码段加载到内存中放在这里;
2. 内存管理
系统堆内存的管理不同的场景有不同的管理方案;
-
TaggedPointer
:NSNumber
之类的对象; -
NONPointer_ISA
: - 散列表:复杂度数据结构, 里面有弱引用表和内存计数表;
里面是
SideTables
结构;SideTable(哈希表)
包含spinlock_t(自旋锁)
,RefcountMap(引用计数表)
,weak_table_t(弱引用表)
几部分;自旋锁:是一个
忙等
的锁(当锁被占用时会不停的检测当前线程去尝试使用, 不会阻塞线程); 适用于轻量级的访问;
引用计数表:通过hash表
实现;原因是为了提高查找效率(因为插入和查找是通过同一个hash
算法进行的, 避免循环遍历);
弱引用表:通过hash
表实现;原因是为了提高查找效率(因为插入和查找是通过同一个hash
算法进行的, 避免循环遍历);
3. MRC和ARC
- 什么是MRC:手动管理引用计数;相关函数有
alloc
,retain
,release
,retainCount
,autorelease
,dealloc
; - 什么是ARC:自动管理引用计数;是编译器(
LLVM
)和runtime
协作的结果;禁止调用retain
,release
,retainCount
,dealloc
等方法;新增weak
,strong
等关键词;
关键词含义:
-
alloc
:一系列调用后最终调用的是C
中的calloc
,alloc
后引用计数并没有置为1; -
retain
:经过(sidetables
,sidetable
)两次hash
查找到后对齐引用计数进行+1操作;
知识点:系统是如何将
weak
变量添加到弱引用表中的?
被声明成__weak
的对象指针在通过编译后会依次调用objc_initWeak()
,storeWeak()
,weak_register_no_lock()
几个函数;最终在weak_register_no_lock()
函数中进行弱引用变量的添加, 添加的位置时通过hash
算法进行位置查找的, 如果查找到相应位置已经有了当前对象所对应的弱引用变量数组就把新的弱引用变量添加到数组, 如果没有则重新创建弱引用数组并在第0个位置添加最新的weak
指针(数组有四个元素:其余的初始化为nil
);
知识点:当
weak
修饰的变量在dealloc
时置为nil
的过程?
对象被dealloc
后, 在dealloc
内部实现中经过一系列的调用最终在weak_clear_no_lock()
函数中根据当前对象指针查找弱引用表,把当前对象相对应的(数组)弱引用取出遍历置为nil
;
4. 自动释放池
什么是自动释放池?
- 自动释放池是
OC
提供的一种回收机制,具有延迟释放的特性- 以
栈
为节点通过双向链表形式组合而成;
(每一个自动释放池没有单独结构, 它是有若干个autoreleasePoolPage
通过双向链表链接而成)- 与线程一一对应(看
autoreleasePoolPage
的结构);
使用场景? 例:在
for
循环中alloc
图片数据等内存消耗较大的场景时手动插入@autoreleasePool
; 每次for
循环结束进行一次内存释放, 防止内存峰值过大;
编译器会将@autoreleasepool()改写为以下代码
void *ctx = objc_autoreleasePoolPush();
{相关代码}
objc_autoreleasePoolPop(ctx);
AutoreleasePoolPage
结构:
class AutoreleasePoolPage {
magic_t const magic;
id *next;//指向栈顶最新被添加进来的autorelease对象的下一个位置
pthread_t const thread;//指向当前线程
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t
}
magic用来校验AutoreleasePoolPage结构是否完整;
next指向第一个可用的地址;
thread指向当前的线程;
parent双向链表中指向父指针;
child双向链表中指向子指针;