内存管理

1,038 阅读5分钟

内存布局

image.png

  • 栈区【一般为0x7开头】
    • 函数
    • 指针
    • 局部变量、方法参数
  • 堆区【一般为0x6开头】
    • 通过alloc分配内存的对象
    • block copy
  • 未初始化数据 .bss【一般为0x1开头】
    • 未初始化的全局变量
    • 未初始化的静态变量
  • 数据段 .data【一般为0x1开头】
    • 初始化的全局变量
    • 初始化的静态变量
  • 代码段 .text
    • 程序代码,加载到内存中

为什么栈中地址访问比堆要快?
因为栈中内存是通过sp寄存器去定位的,而堆区地址需要先通过寄存器定位到包含内存空间的地址,再通过这个地址定位到具体位置。

内存管理方案

TaggedPointer

基础

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

NONPOINTER_ISA

基础

  • 非指针型isa
  • 我们在探究isa_t的结构时,会看到其内部存储了一个ISA_BITFIELD,查看一下arm64架构下的ISA_BITFIELD
#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 unused            : 1;    \   
uintptr_t has_sidetable_rc  : 1;    \   
uintptr_t extra_rc          : 19
  1. nonpointer:是否对isa指针开启指针优化。【0:纯isa指针,1:不只是类对象地址,isa中包含了类信息、对象的引用计数】
  2. has_assoc:关联对象标志位。【0:没有,1:存在】
  3. has_cxx_dtor:该对象是否有C++/Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快地释放对象
  4. shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。
  5. magic:用于调试器判断当前对象是真的对象or没有初始化的空间
  6. weak_referenced:标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放
  7. has_sidetable_rc:当对象的引用计数大于10时,则需要借用该变量存储进位
  8. extra_rc:表示该对象的引用计数值,实际上是引用计数值减1.例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用到上面的has_sidetable_rc:当对象的引用计数大于10时,需要借用该变量存储进位

散列表SideTable

基础

散列表是有多张的,本质是StripedMap<SideTable>结构

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
···
}

根据StripedMap的定义可以知道,iphone上有8张SideTable,其它平台有64张SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;  // 引用计数
    weak_table_t weak_table;  // 弱引用表
}

// weak_table_t
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

MRC & ARC

retain流程探索

当我们创建一个对象的时候,根据断点可以看到会走到_objc_rootRetain方法

image.png

// _objc_rootRetain
id _objc_rootRetain(id obj) {
    ASSERT(obj);
    return obj->rootRetain();
}

// rootRetain
id objc_object::rootRetain() {
    return rootRetain(false, RRVariant::Fast);
}

// 也就是说最终实际调用的是重载的rootRetain方法,入参为false和Fast,下面我们探究该方法流程
id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) {···}
  1. 是否为TaggedPointer,若是,直接返回自身(无引用计数),若不是,继续。
if (slowpath(isTaggedPointer())) return (id)this;
  1. 是否为nonpointer类型,若为nonpointer
    • 判断是否正在析构,若在析构则不执行retain操作
    • extra_rc值+1,若carry则代表newisa.bits存满了导致溢出
// 位运算
uintptr_t carry; // carry代表是否溢出
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
// RC_ONE
#   define RC_ONE   (1ULL<<56)

若溢出,且传入参数不为RRVariant::Full,走到rootRetain_overflow方法,并返回。

if (variant != RRVariant::Full) {
    ClearExclusive(&isa.bits);
    return rootRetain_overflow(tryRetain);
}

// 可以看到该方法内部还是调用了rootRetain,但传入为RRVariant::Full
id objc_object::rootRetain_overflow(bool tryRetain) {
    return rootRetain(tryRetain, RRVariant::Full);
}

若传入参数不为RRVariant::Full,将newisa.extra_rc只保留一半

// 将散列表的flag置为true
sideTableLocked = true;
transcribeToSideTable = true;
// extra_rc只保留一半,且将该对象的has_sidetable_rc置为true
newisa.extra_rc = RC_HALF; // #     define RC_HALF  (1ULL<<18)
newisa.has_sidetable_rc = true;
  1. 若不为nonpointer类型,走到sidetable_retain
return sidetable_retain(sideTableLocked) // false

简单画了个流程图

image.png

weak

当我们用__weak修饰一个变量,断点运行可以看到来到了objc_initWeak方法。

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

下面探究storeWeak方法。

  1. 根据传入的newObj获取散列表
newTable = &SideTables()[newObj];

image.png 2. 创建弱引用表

weak_register_no_lock(&newTable->weak_table,
                      (id)newObj,
                      location,
                      crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);

weak_register_no_lock方法中重点关注以下几行

// 被弱引用对象地址
objc_object *referent = (objc_object *)referent_id;
// 被弱引用对象指针地址
objc_object **referrer = (objc_object **)referrer_id;

// 创建weak_entry_t
weak_entry_t new_entry(referent, referrer);
// 判断是否需要扩容
weak_grow_maybe(weak_table);
// 将新的weak_entry_t添加到弱引用表中
weak_entry_insert(weak_table, &new_entry);

weak_entry_t结构

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        ··· // 由于是联合体位域的结构,我们暂时只关注下面这部分
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    // 初始化方法
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) { // WEAK_INLINE_COUNT为4
            inline_referrers[i] = nil;
        }
    }
}

自动释放池

首先我们将一个空工程通过clang指令查看编译后的产物

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}

// xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return 0;
}

__AtAutoreleasePool结构体中包含了一个构造函数和一个析构函数,分别对应objc_autoreleasePoolPush()函数和objc_autoreleasePoolPop()函数

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

来到objc源码中,可以看到这两个方法分别对应的是AutoreleasePoolPagepush()pop(void *ctxt)方法。首先查看下AutoreleasePoolPage的结构

class AutoreleasePoolPage : private AutoreleasePoolPageData {
friend struct thread_data_t;

public:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
 
private:
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3;
static size_t const COUNT = SIZE / sizeof(id);
static size_t const MAX_FAULTS = 2;
}

// AutoreleasePoolPageData
struct AutoreleasePoolPageData {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;
        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
#endif

magic_t const magic; // 16
__unsafe_unretained id *next; // 8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; // 8
AutoreleasePoolPage *child; // 8
uint32_t const depth; // 4
uint32_t hiwat;  // 4
}
  • magic:用来校验AutoreleasePoolPage的结构是否完整
  • next:指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
  • thread:指向当前线程
  • parent:指向父结点,第一个结点的parent为nil
  • child:指向子结点,最后一个结点的child为nil
  • depth:代表深度,从0开始
  • hiwat:代表high water mark,最大入栈数量标记