1.内存布局
栈区 0x7
创建临时变量时由编译器自动分配,在不需要的时候自动清除的变量的存储区。
里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。
堆区 0x6
那些由 new alloc 创建的对象所分配的内存块,它们的释放系统不会主动去管,由我们的开发者去告诉系统什么时候释放这块内存(一个对象引用计数为0是系统就会回销毁该内存区域对象)。一般一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操作。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。
堆可以动态地扩展和收缩。
静态区(未初始化数据).bss
程序运行过程内存的数据一直存在,程序结束后由系统释放
常量区(已初始化数据).data
专门用于存放常量,程序结束后由系统释放
代码区
用于存放程序运行时的代码,代码会被编译成二进制存进内存的程序代码区
1.堆栈溢出的原因:
堆里面的放new出来的变量,一直往高地址延伸
栈区里面放了一些函数,方法以及临时变量,一直往低地址延伸
当前APP进程分配的内存有限,会有一个临界点
当这两个相遇就会形成堆栈溢出
2.内存管理方案
TaggedPointer:小对象-NSNumber NSDate
- 存储小对象
- 不是一个简单的地址 包含了值和类型以及长度
例如: 0x00000000012 (1代表值 2代表类型)- 速度特别快
NONPOINTER_ISA (非指针型isa)
是联合体,被优化成联合体的位域 每一段里面都代表不同的含义
在不同的架构下具体的分配是不一样的
如果你重写了allocWithZone后没有调用父类 就不是被优化过的ISA了
nonpointer: 表示是否对 isa 指针开启指针优化
0:纯isa指针
1:不止是类对象地址,isa 中包含了了类信息、对象的引⽤用计数等
has_assoc: 关联对象标志位,
0没有
1存在
has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更更快的释放对象
shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤用来存储类指针
magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间 weakly_referenced: 标志对象是否被指向或者曾经指向⼀一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
deallocating: 标志对象是否正在释放内存
has_sidetable_rc:当对象引⽤计数大于 10 时,则需要借⽤该变量存储进位
extra_rc:当表示该对象的引⽤计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引⽤用计数⼤大于 10, 则需要使用到上面的 has_sidetable_rc。
散列表(SideTable):
SideTable 其实是一个 hash 表,下面挂了很多的 SideTable,SideTable 包括自旋锁(Spinlock_t),引用计数表(RefCountMap),弱引用表(weak_table_t)。
SideTable 为什么是多张表,而不是一张表?:
如果只有一张表,如果想操作某一个对象的引用计数,由于不同的对象是在不同的线程操作,由于不同线程需要来操作这张表,所以就有资源访问的问题,那么就需要对这张大表进行加锁操作,如果成千上万对自己进行引用计数操作,那么需要加锁排队,就会有效率问题,所以系统引用了 “分离锁” 概念,比如 A,B同时进行操作的话,可以并发进行,因为A,B,在不同的表中。
如果实现快速分流?找到当前对象在哪张表中?:
SideTable 其实是一张 hash 表,key(对象指针)->hash函数->value(SideTable),通过这个hash计算之后,就可以计算出当前对象在哪个hash表中,也就找到了对应的sideTable
hash 查找:
给定一个内存地址,通过hash计算就可以得到数组的下标地址,f(ptr) = ptr%arr.count,比如内存地址为1,通过上面的就可以找到在数组中的位置
散列表的数据结构:
Spinlock_t:自旋锁
忙等,如果锁已被其他线程获取,那么当前线程会自己去不断的获取是否被释放,直到其他线程释放,适用于轻量访问,如+1,-1。RefCountMap:引用计数表
其实就是hash查找,提高查找效率,插入和查找通过同一个hash函数来获取,避免了循环遍历。ptr->hash->size_t,其中的size_t就是引用计数值,比如用64位存储,第一位表示(weakly_referenced),表示对象是否存在弱引用,下一位表示当前对象是都正在dealloc(deallocating),剩下的位表示引用计数值。weak_table_t:弱引用表
也是一个hash表,key->hash->weak_entry_t,weak_entry_t,其实是一个结构体数组(weakPtr),比如被weak修饰,就存在这个弱引用表中。
3.ARC&MRC
4.引用计数
1. alloc出来引用计数
Person *p = [Person alloc]; // extrac = 0 NSLog(@"%lu",(unsigned long)[p retainCount]); // 1,原理如下图所示⬇️
返回引用计数+1 所有打印的retainCount至少都是1
2.retain原理
执行顺序
<1> - (id)retain {}
<2> objc_object::rootRetain()参数分别:false,false
<3> objc_object::rootRetain(bool tryRetain, bool handleOverflow)
<4> 判断新旧isa是否一致循环,一致就执行<9>,否则执行<5>
<5> 循环获取旧值,并赋给新值,为新值进行extra_rc+1
<6> 判断是否溢出(x86_64 256),没溢出就执行<9>,溢出走<7>
<7> 执行rootRetain_overflow,回到<3>,handleOverflow为true,下次过来时执行<8>
<8> x86_64留下引用计数的一半128,复制另一半存进去散列表
<9> return
// 并且调用retain的时候,传入的两个参数均为false
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
// 循环条件:判断是否独一份存储,对比新旧isa,如果不是,就循环
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
// 如果当前对象的isa 正在销毁
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
//是否溢出,
//经过实验:在x86_64架构下,当newisa.extra_rc为255时,在进行addc,就会发生溢出
//溢出之后,将会拿2的7次方的extra_rc 存到散列表中,newisa.extra_rc回到128
uintptr_t carry;
//这里newisa.extra_rc 会+1 RC_ONE
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
printf("%lu,",newisa.extra_rc);
//newisa.extra_rc++如果溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//第一次来的话,handleOverflow是false,会进判断语句
if (!handleOverflow) {
ClearExclusive(&isa.bits);
//这里重新调用了当前方法rootRetain,但是handleOverflow = true
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
// retry之后会来到这里
// 翻译:留下内部关联对象的一半,准备复制另一半存进去散列表
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 (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
// 拷贝一半(128)进散列表
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
3.release
执行顺序
<1> - (oneway void)release {}
<2> objc_object::rootRelease() 参数分别:true,false
<3> objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
<4> 判断新旧isa是否一致循环,一致就执行return,否则执行<5>
<5> 循环获取旧值,并赋给新值,为新值进行extra_rc-1
<6> 判断是否溢出,没溢出就执行return,溢出走<7> underflow
<7> 判断是否有用到散列表
<8> 从散列表中拿出RC_HALF,将这部分存进newisa
<9> 存成功就return,不成功就重试,再不行就把拿出来的放回去,然后goto retry;
<10> dealloc
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
//新旧isa
isa_t oldisa;
isa_t newisa;
retry:
//跟retain一样的判断条件
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//newisa.extra_rc-1
//如果溢出的时候, newisa.extra_rc = 255
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
//如果溢出走这
printf("释放溢出了,underflow\n");
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
// 重新把旧isa给新isa,意思是把引用计数-1操作还原
// 这时候的 newisa.extra_rc = 0
newisa = oldisa;
// retain的时候。如果有用到散列表,会 newisa.has_sidetable_rc = true;
if (slowpath(newisa.has_sidetable_rc)) {
printf("发现has_sidetable_rc = true \n");
// 调用release的时候handleUnderflow = false
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
//类似retain时候retry,重新来一次,但是handleUnderflow为true
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
// 进判断前 sideTableLocked 没有重新赋值,所以一直是false
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
// 去retry,重新回到上面,重复走一遍
goto retry;
}
// Try to remove some retain counts from the side table.
// 从散列表中拿出RC_HALF的引用计数
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
printf("借出来的 size === %lu \n",borrowed);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
// 把拿出来的引用计数存到newisa
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
//如果没存成功,就换个姿势再试试
// 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.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// 如果还是没成功,把拿出来的放回去
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
// 如果newisa.has_sidetable_rc != true;
// 就抛错,release太多
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
retain、release总结
现在isa是NONPOINTER_ISA,他是按位进行存储的,我们引用计数的存储在2个位置,一个在extra_rc,第二个在散列表里面。
retain:是会+1的 extra_rc + 1。extra_rc只有8个位置(X86下)当超出的时候就会carry 也就是上溢出 就会把1半的空间往散列表里面丢,如果散列表里面都满了那么这个时候会发生容量的变化。
relase: extra_rc -1 ,如果extra_rc为0的时候,在减1的时候就会是下溢出,首先会判断散列表,如果散列表里面会借用一半的RC_HALF-1 存到extra_rc里面,如果散列表里面也没有,就会发生下溢出,就触发析构函数。
- dealloc
应该做一些什么事情?
free函数 - 释放对象
weak弱引用计数表 - 处理
关联对象 associated
5.弱引用
不操作引用计数
散列表里面有weak 对象
1:weak_register_no_lock
2:从我们的散列表的 weak_table 哈希表—>weak_entry_t *entry;
3: weak_entry_t *weak_entries 下标 下的 entry
4:entry->inline_referrers[i] = new_referrer;
5:new_referrers[i] = entry->inline_referrers[i]
6:entry->referrers = new_referrers;
总结
sidetabels — sidetable - 弱引用表
weakTable - entry - 数组 - 弱引用对象指针
更详细的解析
6.自动释放池
什么时候用到
自动释放池 : 容纳变量 - 释放
1:大量的临时变量
2:非UI操作, 命令行
3:自己创建辅助线程
原理
__AtAutoreleasePool __autoreleasepool;
objc_autoreleasePoolPush --> atautoreleasepoolobj
objc_autoreleasePoolPop
AutoreleasePoolPage::push();
page:属性 56个字节
一页所能容乃的大小: 4096
// 505 满
// 38 = 3*16+8 = 48+8 = 56
// 16进制 8
// 0x103803038 边界 -- 加进去 压栈 -- 剪出来 出栈 - 页面销毁
自动释放池 - 双向链表
这一页是不是满 --
4096 - 属性 56 push 压栈 + 边界符 - 开辟新的页面 压栈