内存管理

内存分配方式

内存管理_第1张图片

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
        (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 不会增加引用计算,如果被修饰的对象在某一时刻被释放,会产生悬垂指针导致内存泄露

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