美文网首页
二十七、ios内存管理

二十七、ios内存管理

作者: Mjs | 来源:发表于2020-11-25 16:13 被阅读0次

内存布局

内存分配.png

0xc0000000换算一下就是3GB,这3GB交给系统程序的使用,还有1GB是给内核区使用。剩下的0x00400000作为保留字段预留给系统,比如nil、底层发送信号之类的。

栈区:函数,⽅法
堆区:通过alloc分配的对象,block copy
BSS段:未初始化的全局变量,静态变量
数据段: 初始化的全局变量,静态变量
text:程序代码,加载到内存中

栈区内存地址:⼀般为:0x7开头
堆区内存地址:⼀般为:0x6开头
数据段,BSS内存地址:⼀般为:0x1开头

内存管理⽅案

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

TaggedPointer
  • taggedpointer指针:既包含了指针,又包含了值。
  • 对象:包含了值和指针地址。
  • 小对象: 对int、float之类的值包装成对象,例:NSNumber,NSData之类很小的对象

我们先看一个程序


    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"abc"];  // alloc 堆 iOS优化 - taggedpointer
             NSLog(@"%@",self.nameStr);
        });
    }
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"壮志饥餐胡虏肉,笑谈渴饮匈奴血。"];
            NSLog(@"%@",self.nameStr);
        });
    }

这个两个异步线程的循环,上面那个可以正常运行,而下面那个运行到一半就会发生奔溃。这是因为下面的程序多条线程,同时对一个对象释放。我们通过断点可以看到上面的string 是NSTaggedPointerString类型,下面的string是__NSCFString类型。当对象是TaggedPointer类型时不会进行retain和realease操作,所以不会发生崩溃。
在类的加载时,我们就会对_read_imagesTaggedPointer进行处理

static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation.
        DisableTaggedPointerObfuscation) {
        objc_debug_taggedpointer_obfuscator = 0;
    } else {
        // Pull random data into the variable, then shift away all non-payload bits.
        arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                       sizeof(objc_debug_taggedpointer_obfuscator));
        objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
    }
}

现在的小对象地址都是进行了混淆处理之后的。

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

    NSString *str2 = [NSString stringWithFormat:@"b"];
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));


    NSNumber *number1 = @1;
    NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
········
0xa000000000000621
__NSCFNumber-0xb2d6852fbc115960-1 - 0xb000000000000012

我们将一个小对象进行解码打印之后可以看到他的结构,不仅是他的地址,他的值也包含其中。

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

#   define _OBJC_TAG_MASK (1UL<<63)

这里通过mask,来判断最高位是1的就是TaggedPointer
0xa -> 1 010;010->2
0xb -> 1 011;011->3

typedef uint16_t objc_tag_index_t;
enum
#endif
{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 
...
};

0xa就是NSString类型,0xa就是NSNumber 类型。
小对象不需要arc进行管理,常量区可以直接释放回收。

  1. Tagged Pointer专⻔⽤来存储⼩的对象,例如NSNumber和NSDate
  2. Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再
    是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储
    在堆中,也不需要malloc和free
  3. 在内存读取上有着3倍的效率,创建时⽐以前快106倍。
小对象包含范围.png
NONPOINTER_ISA
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)
  • nonpointer:表示是否对 isa 指针开启指针优化
    0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等
  • has_assoc:关联对象标志位,0没有,1存在
  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
  • shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。
  • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced:此对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存
  • has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位
  • extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,
    例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的 has_sidetable_rc。`
散列表(SiddeTables)
struct SideTable {
    spinlock_t slock;//开解锁
    RefcountMap refcnts;//引用技术表
    weak_table_t weak_table;//弱引用表
};

散列表在内存中有8张。
如果只有一张,所有的对象都在一个表中,每次开锁,所有的都会暴露出来。若果每个对象都开一个表就会过度消耗性能。

#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
delloc
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

如果没有满足条件直接free。

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    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 = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);//清除cxx
        if (assoc) _object_remove_assocations(obj);//移除关联对象
        obj->clearDeallocating();
    }
    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();//散列表清除
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);//弱引用表清除
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
dealloc.png
retain

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            //直接操作散列表
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {//操作散列表
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                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.
            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 (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();
    return (id)this;
}



# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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 deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

addc方法之前说过,在这里就是找到extra_rc,然后进行+1操作。如果满了,就会走carry。
如果extra_rc溢出,就会将extra_rc的一半交给散列表。

retainCount

        NSObject *objc = [NSObject alloc];
        
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
···················
1

我们在分析alloc源码的时候并没有看到对extra_rc的操作,打印的结果却是一。
查看retainCount源码

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

系统会在获取retainCount事检测是否为nonpointer,然后对其进行加一操作。

相关文章

网友评论

      本文标题:二十七、ios内存管理

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