iOS底层原理(34)-内存管理(上)

229 阅读2分钟

一、内存布局

image.png

1、内核区

用于系统级别的操作空间。

2、栈区

存放局部变量、方法参数、函数、方法指针

空间小

内存地址:一般为0x7开头;

从高到低分配空间

栈区内存如何定位? 是通过sp寄存器定位的。

3、堆区

存储对象、需要开辟空间(alloc、new、block copy等)

空间大

内存地址:一般为0x6开头;

4、全局区

  • 未初始化数据(.bss)

      未初始化的全局变量、静态变量;
    
  • 已初始化数据(.data)

      初始化的全局变量、静态变量;
    
  • 代码段(.txt)

      程序代码,加载到内存中;
    

内存地址:一般为0x1开头;

5、保留区

测试代码:内存五大区测试代码

二、内存管理方案

  • Arc
  • Mrc
  • TaggedPointer:⼩对象-NSNumber,NSDate
  • NONPOINTER_ISA:⾮指针型isa
  • 散列表:引⽤计数表,弱引⽤表

1、TaggedPointer

1:Tagged Pointer专⻔⽤来存储⼩的对象,例如NSNumber和NSDate

2:Tagged Pointer指针的值不再是地址了,⽽是真正的是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。在堆中,也不需要malloc和free

3.在内存读取上有着3倍的效率,创建时⽐以前快106倍。

WWDC看taggedPointer

WWDC介绍

image.png

x86-64下的taggPointer

image.png

arm64下的taggedPointer

image.png Screen Shot 2022-04-12 at 21.29.01.png

image.png

tagggedPointer 面试题

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.jim.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"jim"];
             NSLog(@"%@",self.nameStr);
        });
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"来了");
    // 多线程 读和写
    // setter -> retian release
    for (int i = 0; i<100000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"我是一个非常长的字符串,请知悉"];
            NSLog(@"%@",self.nameStr);
        });
    }
}

上面的代码运行会发生崩溃,因为上面的nameStrTaggedPointer类型,下面的是NSCFString类型,这是在多线程环境下操作,会进行频繁的,写操作本质就是 setter方法,而setter内部会进行旧值的release和新值的retain,那么问题来了,TaggedPointer类型是小对象,在底层是不进行retain、 release 操作的,而上面的NSCFString类型在异步条件下,可能会连续多次release,因此导致崩溃。

字符串的类型 image.png

2、Arc&Mrc

arc和mrc通过引用计数来管理内存,那么引用计数存在哪里? isa里面。

ISA的结构

#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等

has_assoc:关联对象标志位,0没有,1存在

has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象

shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。(相当于taggedPointer里面的payload)

magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间

weakly_referenced:指对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。

deallocating:标志对象是否正在释放内存

has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位

extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc。

探索 retain

retain
inline id
objc_object::retain()
{
    ASSERT(!isTaggedPointer());

    return rootRetain(false, RRVariant::FastOrMsgSend);
}
rootRetain
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    
    //如果是TaggedPointer ,就不做retain处理
    if (slowpath(isTaggedPointer())) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    oldisa = LoadExclusive(&isa.bits);

    /* 不需要看
    if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

    if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }
*/
    
    //重点内容
    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);//将散列表里面的引用计数拿出来
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }
        uintptr_t carry;//代表引用计数的数量
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            //extra_rc 引用计数加满了,溢出了
            if (variant != RRVariant::Full) {
                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.
            // extra_rc里面留一半,sidetable里面存一半
            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 (variant == RRVariant::Full) {
        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();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}
sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        //#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
        //这里加2,跟引用计数 存在sidetable哪个地方有关,存在倒数第二位,那么加2,就是倒数第二位加1
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

探索 release

跟retain 基本上一样,再次不做赘述。