美文网首页ios 开发
iOS内存管理(一)基础知识

iOS内存管理(一)基础知识

作者: iOS小洁 | 来源:发表于2020-12-17 16:56 被阅读0次

    iOS内存管理

    内存分区

    这里从低到高介绍一下iOS内存的分区及主要存储内容。

    • 内核区
    • 栈区:函数,方法,局部变量
    • 堆区:通过alloc分配的对象
    • 数据段
      1. 字符串常亮:如"abc"
      2. 未初始化数据:未初始化的全局变量量,静态变量量
      3. 已初始化数据:初始化的全局变量量,静态变量量
    • 代码区:程序代码,加载到内存中
    • 保留区

    程序运行过程中数据主要是存在堆区和栈区的,而我们的内存管理管理的是堆上的内存,栈上的内存一般情况下不需要我们管理。

    Tagged Point

    Tagged Point是从64位开始才有的,目的是优化NSNumber、NSDate、NSString 等小对象的存储。

    Tagged Pointer的指针不像其他对象指针指向一个地址,Tagged Pointer指针包含真正的值。所以,它的内存没有存在堆中,也不不需要malloc和free

    优势是读取速度更快,更省空间。

    引用计数MRC ARC

    概念:引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。

    iOS中采用了引用计数方案管理内存,分为MRC 手动引用计数 和 ARC 自动引用计数。 MRC时代需要程序员手动管理对象的生命周期,也就是对象的引用计数有程序员来控制,什么时候retain,什么时候release,完全自己掌握.ARC自动引用计数是编译器的一个特性,能够自动管理OC对象内存生命周期.在ARC中你需要专注于写你的代码, retain ,release, autorelease操作交给编译器去处理就行了.

    ARC中禁⽌止⼿手动调⽤用retain/release/retainCount/dealloc。但是新加了weak、strong属性关键字

    引用计数存储位置在哪里呢,我们先看下源码,源码下载地址 https://opensource.apple.com

    // SUPPORT_NONPOINTER_ISA
    
    inline uintptr_t 
    objc_object::rootRetainCount()
    {
        if (isTaggedPointer()) return (uintptr_t)this;
    
        sidetable_lock();
        isa_t bits = LoadExclusive(&isa.bits);
        ClearExclusive(&isa.bits);
        if (bits.nonpointer) {
            uintptr_t rc = 1 + bits.extra_rc;
            if (bits.has_sidetable_rc) {
                rc += sidetable_getExtraRC_nolock();
            }
            sidetable_unlock();
            return rc;
        }
    
        sidetable_unlock();
        return sidetable_retainCount();
    }
    

    我看这个版本号是objc4-787.1,通过源码能够看出在retaincount方法中判断几点:1、isTaggedPointer,2、bits.nonpointer,3、bits.has_sidetable_rc。

    首先isTaggedPointer这个前面已经说了,是对小对象类型的优化,指针就代表数据,没有isa指针;然后bits.nonpointer判断的是是否是优化过得isa指
    针;bits.has_sidetable_rc判断是否使用sidetable存储引用计数。

    这里要注意的是intptr_t rc = 1 + bits.extra_rc;这行代码,bits.extra_rc和sidetable中存储的值比实际引用计数小1。也就是说并不是bits.extra_rc为0且sidetable中没有存储引用计数信息时,对象并不会被释放。而是在存储为0的时候再调用release才会释放。

    下面依次来讲一下isa和sidetable。

    isa

    isa又是对指针的优化,这里放上一部分源码

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    

    这个只截取了arm64位定义的,后面的数字代码位数。加起来正好是64位,标识各个信息在isa中占用的位数。目的还是节省空间。下面简单介绍一下isa中包含的信息

    • nonpointer 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址,1,代表优化过,使用位域存储更多的信息
    • has_assoc 是否有设置过关联对象,在dealloc过程中会判断
    • has_cxx_dtor 是否有C++的析构函数(.cxx_destruct)
    • shiftcls 存储着Class、Meta-Class对象的内存地址信息
    • magic 用于在调试时分辨对象是否未完成初始化
    • weakly_referenced 是否有被弱引用指向过,如果没有,释放时会更快
    • deallocating 对象是否正在释放
    • has_sidetable_rc 引用计数器是否过大无法存储在isa中
    • extra_rc 里面存储的值是引用计数器减1

    sidetable

    关于引用计数的存储,就是在isa. extra_rc 和 sidetable 中。还是先放一下sidetable定义的源码

    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts;
        weak_table_t weak_table;
    
        SideTable() {
            memset(&weak_table, 0, sizeof(weak_table));
        }
    
        ~SideTable() {
            _objc_fatal("Do not delete SideTable.");
        }
    
        void lock() { slock.lock(); }
        void unlock() { slock.unlock(); }
        void forceReset() { slock.forceReset(); }
    
        // Address-ordered lock discipline for a pair of side tables.
    
        template<HaveOld, HaveNew>
        static void lockTwo(SideTable *lock1, SideTable *lock2);
        template<HaveOld, HaveNew>
        static void unlockTwo(SideTable *lock1, SideTable *lock2);
    };
    

    里面有spinlock_t,这是个自旋锁,保证多线程安全的;RefcountMap就是引用计数表;weak_table_t 弱引用表。

    retain,release

    上面大概介绍了一下引用计数,isa,sidetable关系的概念,可以看出他们三者是息息相关的,那么具体的关系是什么么。可以看下retain,release相关源码

    objc_object::rootRetain(bool tryRetain, bool handleOverflow)
    {
        if (isTaggedPointer()) return (id)this;
    
        bool sideTableLocked = false;
        bool transcribeToSideTable = false;
    
        isa_t oldisa;
        isa_t newisa;
    
        do {
            transcribeToSideTable = false;
            oldisa = LoadExclusive(&isa.bits);
            newisa = oldisa;
            if (slowpath(!newisa.nonpointer)) {
                ClearExclusive(&isa.bits);
                if (rawISA()->isMetaClass()) return (id)this;
                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
            if (slowpath(tryRetain && newisa.deallocating)) {
                ClearExclusive(&isa.bits);
                if (!tryRetain && sideTableLocked) sidetable_unlock();
                return nil;
            }
            uintptr_t carry;
            newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
    
            if (slowpath(carry)) {
                // newisa.extra_rc++ overflowed
                if (!handleOverflow) {
                    ClearExclusive(&isa.bits);
                    return rootRetain_overflow(tryRetain);
                }
                // Leave half of the retain counts inline and 
                // prepare to copy the other half to the side table.
                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.
            sidetable_addExtraRC_nolock(RC_HALF);
        }
    
        if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
        return (id)this;
    }
    
    

    retain中主要做的事情是isa.extra_rc内保存引用计数+1,当isa.extra_rc上溢出时,将其一般引用计数放进sidetable中,并将has_sidetable_rc 置为 true。

    ALWAYS_INLINE bool 
    objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
    {
        if (isTaggedPointer()) return false;
    
        bool sideTableLocked = false;
    
        isa_t oldisa;
        isa_t newisa;
    
     retry:
        do {
            oldisa = LoadExclusive(&isa.bits);
            newisa = oldisa;
            if (slowpath(!newisa.nonpointer)) {
                ClearExclusive(&isa.bits);
                if (rawISA()->isMetaClass()) return false;
                if (sideTableLocked) sidetable_unlock();
                return sidetable_release(performDealloc);
            }
            // don't check newisa.fast_rr; we already called any RR overrides
            uintptr_t carry;
            newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
            if (slowpath(carry)) {
                // don't ClearExclusive()
                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
        newisa = oldisa;
    
        if (slowpath(newisa.has_sidetable_rc)) {
            if (!handleUnderflow) {
                ClearExclusive(&isa.bits);
                return rootRelease_underflow(performDealloc);
            }
    
            // Transfer retain count from side table to inline storage.
    
            if (!sideTableLocked) {
                ClearExclusive(&isa.bits);
                sidetable_lock();
                sideTableLocked = true;
                // Need to start over to avoid a race against 
                // the nonpointer -> raw pointer transition.
                goto retry;
            }
    
            // Try to remove some retain counts from the side table.        
            size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
    
            // 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
                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.
    
        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();
    
        __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
    
        if (performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
        }
        return true;
    }
    
    

    release 中主要做的是将isa.extra_rc 减1,当isa.extra_rc下溢是进入underflow中,在underflow中判断has_sidetable_rc。如果为flase,调用dealloc去释放对象;如果为true,将sidetable中的引用计数挪出一部分给isa.extra_rc,然后重新回到retry。

    自动释放池

    AutoreleasePoolPage类内容太多,不全部贴出来了,在NSObject.mm文件中定义。调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的.

    class AutoreleasePoolPage 
    {
        magic_t const magic;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    

    每个AutoreleasePoolPage对象占用4096字节内存,除了固定存储内部的成员变量外,其余空间用来存放autorelease对象的地址

    调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址

    调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY

    id *next指向了下一个能存放autorelease对象地址的区域

    autoreleasepoolpage.png
    Runloop和Autorelease

    iOS在主线程的Runloop中注册了2个Observer
    第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
    第2个Observer
    监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

    相关文章

      网友评论

        本文标题:iOS内存管理(一)基础知识

        本文链接:https://www.haomeiwen.com/subject/edxagktx.html