美文网首页
深入理解内存管理(二)

深入理解内存管理(二)

作者: juriau | 来源:发表于2020-06-22 20:09 被阅读0次

目录

  • 1.Tagged Pointer(带标记的指针)
  • 2.引用计数及weak的实现
    • 1.isa
    • 2.SideTables
    • 3.SideTable
    • 4.引用计数表
    • 5.弱引用表
  • 4.autorelease的实现
    • 1.__AtAutoreleasePool
    • 2.AutoreleasePoolPage
    • 3.自动释放池的释放时机
  • 附录
    • weak源码

一、Tagged Pointer(带标记的指针)

特点:

  • 1.Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
  • 2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。
  • 3.在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。

实验:

NSNumber *n1 = @1; // 0x38548fccceb58159
NSNumber *n2 = @2; // 0x38548fccceb58259
NSNumber *n3 = @3; // 0x38548fccceb58359

NSNumber *largeNumber = @(0xFFFFFFFFFFFFFFF); // 0x100604460,堆空间,正常的对象

Q:请问以下两段代码的运行结果

代码1会崩溃,代码2正常运行。

  • 代码1创建的字符串是一个对象;
    setter方法会先对原对象release,然后将新对象retain赋值给成员变量;
    并发调用setter方法可能会出现多个线程同时调用release的情况,导致崩溃。

  • 代码2创建的字符串是一个Tagged Pointer;
    objc_messageSend内部会判断是不是普通的指针,如果是Tagged Pointer,就直接在指针中取出数值进行赋值。

二、引用计数

1、isa

isa的位域结构体中的extra_rc,存储着对象的引用计数。如果引用计数较大位域存储不下的话,会设置has_sidetable_rc为1,将剩下的引用计数存储在对象对应的引用计数表中。

isa的位域结构体中的weakly_referenced,记录对象是否被弱引用指向过。

2. SideTables

Q:为什么不是一个SideTable?
A:在多线程的环境下,多个对象同时操作时,会存在效率问题!

SideTables的本质是一个Hash表:


3. SideTable

Q:为什么用自旋锁?
因为读写操作很快,所以适合用自旋锁。

4.引用计数表

引用计数表(RefountMap)是一个Hash表:它存放了多个对象的信息,每个对象对于一个引用计数size_t。

5.弱引用表

弱引用表(weak_table_t)是一个Hash表:它存放了多个对象的信息,每个对象对应了一个weak_entry_t,里面有被引用的对象和弱引用数组。

四、autorelease的实现

// 源OC代码:
@autoreleasepool {
    MJPerson *person = [[[MJPerson alloc]init] autorelease];
}
// 编译后:
{
    __AtAutoreleasePool __autoreleasepool;
    MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}

自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。

1.__AtAutoreleasePool

结构:

使用:

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

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

2.AutoreleasePoolPage

结构:

  • AutoreleasePoolPage对象之间是通过双向链表的形式连接在一起。

  • 每个AutoreleasePoolPage对象的结构是:一部分空间用来存放它内部的成员变量,比如指向下一个page指针、指向下一个page的指针。剩下是一个栈,用来存放autorelease对象的地址。

  • 因为是后进先出的栈,所以很容易的实现多个@autorelease嵌套的情况。

Q:AutoreleasePool的实现原理?/ AutoreleasePool的结构

自动释放池的底层实现主要用到两个数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。

AutoreleasePoolPage对象之间以双向链表连接,对象内部出了存放它内部成员变量,剩下的空间就是一个栈,用来存放autorelease对象的地址。

__AtAutoreleasePool用来记录该自动释放池的boundary。方便pop。

3.自动释放池的释放时机

(1)如果被@autoreleasepool包住的话,在大括号结束的时候会释放。
(2)Runloop中的自动释放池

iOS在主线程的Runloop中注册了2个Observer:

  • 第一个Observer监听了"进入Runloop"事件;
  • 第一个Observer监听了"即将休眠"事件和"退出Runloop"事件。

具体过程如下:

  • 在"进入Runloop"时,会调用Push()
  • 在"即将休眠"时,会调用Pop() 和 Push()
  • 在"退出Runloop"时,会调用Pop()

Q:autorelease的释放时机?
会在Runloop即将休眠和退出Runloop时调用pop()

Q:线程中有autorelease pool吗? / 一个 thread中必须有一个autoreleasepool吗?

线程和runloop时一一对应的。不过子线程默认不开启runloop。
在Runloop中, 系统会隐式创建一个Autorelease pool;


附录:weak源码

weak 相关的数据结构

struct weak_table_t {
    weak_entry_t *weak_entries; // 哈希表
    size_t    num_entries; // hash数组中的元素个数
    uintptr_t mask;
    uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数
};

// 哈希表中每一项的元素
struct weak_entry_t {
    // 被引用
    DisguisedPtr<objc_object> referent;
    union {
        // 动态数组
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        // 定长数组
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

根据对象获取它对应的weak表:

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    ASSERT(referent);

    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    // 计算出对象的哈希值
    size_t begin = hash_pointer(referent) & weak_table->mask;
    
    size_t index = begin;
    size_t hash_displacement = 0;
    
    // 发生哈希碰撞
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        if (index == begin) bad_weak_table(weak_table->weak_entries);
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }
    
    return &weak_table->weak_entries[index];
}

相关文章

网友评论

      本文标题:深入理解内存管理(二)

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