内存管理

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了

arm架构下.png

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,原理如下图所示⬇️
image.png

返回引用计数+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里面,如果散列表里面也没有,就会发生下溢出,就触发析构函数。

  1. 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 压栈 + 边界符 - 开辟新的页面 压栈

你可能感兴趣的:(内存管理)