内存管理

内存分配方式

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容