内存管理

作者: 一只长毛猫 | 来源:发表于2018-04-16 23:43 被阅读82次

    内存分配方式

    Objective-C内存管理方式

    1、TaggedPointer

    iPhone5s开始采用64bit cpu架构,编译器通常分配给一个指针的大小就是64bit。一个NSNumber通常不需要8个字节,4个字节就够了。4个字节有符号的整数最大值2^31为20多亿。所以把指针拆成2个部分,一部分保存数据值,一部分做特殊标记,指明这是一个特别的指针,不指向任何一个地址。这样就省去了对象的内存分配,引用计数维护,管理生命周期等操作
    TaggedPointer
    1 专门用于存储小对象,例如NSNumber NSDate
    2 TaggedPointer指针值不再是地址,而是真正的值。
    3 节省内存,提高执行效率。
    判断对象是否在使用TaggedPointer,是看标志位是否为1

    inline bool 
    objc_object::isTaggedPointer() 
    {
        return ((uintptr_t)this & TAG_MASK);
    }
    
    2、isa指针 (NONPOINTER_ISA)

    非指针型isa : 值的部分代表class地址
    指针型isa:值代表class地址
    64 bit存储一个内存地址显然是种浪费。于是可以优化存储方案,用一部分额外的存储空间存储其他内容。isa是objc_object的一个私有成员,它的结构如下:

    union isa_t 
    {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
         struct {
            uintptr_t indexed           : 1;
            uintptr_t has_assoc         : 1;
            uintptr_t has_cxx_dtor      : 1;
            uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
            uintptr_t magic             : 6;
            uintptr_t weakly_referenced : 1;
            uintptr_t deallocating      : 1;
            uintptr_t has_sidetable_rc  : 1;
            uintptr_t extra_rc          : 8;
        };
        ……
    };
    
    变量名 含义
    indexed 0表示普通的isa,1表示使用优化的存储引用计数
    has_assoc 对象是否包含associated object
    has_cxx_dtor 该对象是否有 C++ 或 ARC 的析构函数
    shiftcls 类的指针
    magic 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化
    weakly_referenced 该对象是否有过 weak 对象
    deallocating 该对象是否正在析构
    has_sidetable_rc 是否使用了引用计数表sideTable
    extra_rc 存储引用计数值减一后的结

    猜测:TaggedPointer是把值存在指针当中,不给指针所指的对象分配内存。NONPOINTER_ISA是给对象分配了内存空间,但是不使用sideTable管理引用计数,而是把引用计数存在isa当中。

    3、散列表

    根据key查找内存存储位置的数据结构。通过key得到存储位置的函数是散列函数,存放记录的数组称为散列表。具体实现原理:数组长度固定比如是arr[20],key=abc通过hash函数后得到一个整数n,n%20 == 14. 此时通过key=abc就得到了arr[14]. arr[14]中存放了一个链表的头指针,通过遍历链表获取key相等的就是要找的值。
    SideTable包含了引用计数表,弱引用计数表,以及一个自旋锁。结构如下

    struct SideTable {
        spinlock_t slock;
        RefcountMap refcnts;
        weak_table_t weak_table;
        ……
    }
    

    自旋锁原理:如果锁被其他线程获取,当前线程会不断去探测锁是否被释放。若有释放,会第一时间去获取这个锁。普通的锁线程没有获取到会从用户态切换为内核态进行休眠,而使用自旋锁的线程不会休眠,只会忙等。适用于轻量访问(加一、减一)。

    SideTable &table = SideTables()[this];
    size_t &refcntStorage = table.refcnts[this];
    //refcntStorage  就是引用计数 两次hash查找
    

    引用计数表:实际是用hash表实现的。应用计数会存在多张sideTable中。修改引用计数,需要经过两次hash算法,第一次是从sideTables中找到具体的sideTable。第二次是从sideTable中找到对应的引用计数。之所以设计成多张sideTable而不是一张sideTables,是因为每次操作都需要加锁,减锁操作。多张可以分离锁,加快操作速度。

    struct weak_table_t {
        weak_entry_t *weak_entries;
        size_t    num_entries;
        uintptr_t mask;
        uintptr_t max_hash_displacement;
    };
    

    弱引用计数表:苹果使用sideTables保存所有的weak引用。key就是对象,weak_entry_t作为值。weak_entry_t中保存了所有指向该对象的弱引用。

    引用计数管理

    dealloc的操作

    调用dealloc函数,实际会调用到rootDealloc()。从函数中可以看出,如果使用了TaggedPointer就直接返回,交给栈自己处理。若果是普通的isa,没有弱引用对象,没有关联对象,没有使用c++和ARC的析构函数,没有使用引用计数表,那么直接调用free释放。 否则调用object_dispose,先销毁c++对象,然后移除关联对象,最后清除弱引用表和引用计数表。

    inline void
    objc_object::rootDealloc()
    {
        assert(!UseGC);
        if (isTaggedPointer()) return;
    
        if (isa.indexed  &&  
            !isa.weakly_referenced  &&  
            !isa.has_assoc  &&  
            !isa.has_cxx_dtor  &&  
            !isa.has_sidetable_rc)
        {
            assert(!sidetable_present());
            free(this);
        } 
        else {
            object_dispose((id)this);
        }
    }
    
    
    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
    
        objc_destructInstance(obj);
        
    #if SUPPORT_GC
        if (UseGC) {
            auto_zone_retain(gc_zone, obj); // gc free expects rc==1
        }
    #endif
    
        free(obj);
    
        return nil;
    }
    
    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = !UseGC && obj->hasAssociatedObjects();
            bool dealloc = !UseGC;
    
            // This order is important.
            if (cxx) object_cxxDestruct(obj);
            if (assoc) _object_remove_assocations(obj);
            if (dealloc) obj->clearDeallocating();
        }
    
        return obj;
    }
    
    obj->clearDeallocating(); //中包含了下面2行代码
    weak_clear_no_lock(&table.weak_table, (id)this);
    table.refcnts.erase(this);
    
    weak引用

    id __weak obj1 = obj; 经过编译器会调用

    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);
    }
    

    对象被销毁时,调用dealloc中清除弱引用方法。
    第一次hash(obj)得到sideTables中具体的sideTable
    第二次hash(obj)从sideTable中的weak_table获取具体的weak_entry_t。
    The global weak references table. Stores object ids as keys,and weak_entry_t structs as their values. 找到对象的弱引用数据,遍历置为nil.

    自动释放池

    @autoreleasepool{} 实际是:

    void *ctx = objc_autoreleasePoolPush();
     //code
     objc_autoreleasePoolPop(ctx);
    

    objc_autoreleasePoolPush具体操作:
    1 在AutoreleasePoolPage的next位置插入哨兵nil
    2 在哨兵后面位置add(obj)
    3 位置不够,创建新的AutoreleasePoolPage,然后add(obj)

    objc_autoreleasePoolPop具体操作:
    1 根据传入的哨兵对象找到对应位置
    2 给上次push操作后的对象依次发送release消息
    3 回退next指针到正确的位置

    AutoreleasePoolPage数据结构

    id *next;   //栈的下个位置
    pthread_t const thread;  //当前线程
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    

    本质是:以栈为节点(AutoreleasePoolPage),通过双向链表的形式组合而成 。和线程一一对应。

    问题1: viewDidLoad中 NSMutableArray *array = [NSMutableArray array];何时释放?
    答:在当次RunLoop循环结束后调用AutoreleasePoolPage:pop()时。
    问题二:autoreleasepool多层嵌套?
    多层嵌套就是多次插入哨兵对象。
    问题三:手动插入autorealeasepool
    for循环中alloc图片数据等内存消耗大的场景插入
    问题四:实现原理
    已AutoreleasePoolPage栈为节点的,双向链表的数据结构组合而成

    NSTimer循环引用

    VC +++++++++> banner--------->NSTimer
    NSRunLoop+++++++++>NSTimer+++++++++>banner 导致了VC dealloc后,banner任然没被释放。
    解决方案:引入中间层
    VC +++++++++>banner-------> 中间层 ------->NSTimer
    NSRunLoop+++++++++>NSTime------------>中间层

    ARC

    是由LLVM编译器和Runtime共同协作来为我们实现自动引用计数的管理。

    MRC :手动引用计数
    alloc retain release retainCount autorelease dealloc
    ARC: 自动引用计数
    ARC是LLVM和Runtime协作结果
    ARC禁止手动调用retain/release/retainCount/dealloc
    ARC中新增weak、strong属性关键字

    __weak __block __unsafe_unreatined
    __block 在MRC下,不会增加引用计算,避免循环引用
    在ARC下,会被强引用,无法表面循环引用
    __unsafe_unreatined 不会增加引用计算,如果被修饰的对象在某一时刻被释放,会产生悬垂指针导致内存泄露

    相关文章

      网友评论

        本文标题:内存管理

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