专栏首页MelonTeam专栏Objective-C内存管理原理探究(一)

Objective-C内存管理原理探究(一)

导语 让我们通过源代码了解OC内存管理的机制。

前言

相信每个人在开发iOS的过程中都有过OC是如何管理内存的疑问,虽然大家都知道是基于引用计数的,但retain,release究竟做了什么,只是简单的将引用计数加减1吗?Autorelease又究竟做了什么?Weak又是怎样实现的?等等跟内存相关的问题~本系列文章就从源代码级别来探究下OC究竟是怎么管理内存的~计划分为三篇 1.基础引用计数方法探究 2.ARC内存管理探究 3.Autorelease实现探究 本文是第一篇~

本文使用的源代码是objc4-709

一、引用计数

说起OC的内存管理必须要先说下引用计数: 1.我们创建一个新对象时,该对像引用计数为1; 2.有一个新的指针关联到该对象时,他的引用计数就加1; 3.对象关联的某个指针不再指向他时,他的引用计数就减1; 4.对象的引用计数为0时,说明此对象不再被任何指针指向,这时我们就可以将对象销毁。 这便是引用计数。

二、alloc、init、retain,release实现

alloc实现

OC中声明一个NSObjec是总是会先调用下alloc,但这个alloc究竟是做了什么呢?

在NSObject.mm中可以找到alloc的实现

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

大概流程如下: 1)slowpath(checkNil && !cls)

看下slowpathfastpath的定义

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect起的是优化性能的作用 __builtin_expect((x),1) 表示 x 的值为真的可能性更大; __builtin_expect((x),0) 表示 x 的值为假的可能性更大。

从宏的定义中可以看出slowpath(checkNil && !cls)是对cls进行nil判断,但这里cls大概率不为nil所以这里使用slowpath

2)通过hasCustomAWZ,判断是否有自定义allocWithZone实现

    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }



#if FAST_HAS_DEFAULT_AWZ
    bool hasDefaultAWZ() {
        return getBit(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        setBits(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        clearBits(FAST_HAS_DEFAULT_AWZ);
    }
#else
    bool hasDefaultAWZ() {
        return data()->flags & RW_HAS_DEFAULT_AWZ;
    }
    void setHasDefaultAWZ() {
        data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

hasCustomAWZ用来判断是否有自定义的的allocWithZone方法,如果为YES则说明有自定义实现,则不能走默认逻辑。 hasCustomAWZ为YES时直接调用allocWithZone方法。

// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}

可以看出allocWithZone实际上调用的也是class_createInstanceFromZone

3)hasCustomAWZ为NO时,还需要再次判断当前的class是否支持快速alloc。如果支持,直接调用calloc函数,申请bits.fastInstanceSize()大小的内存空间,如果创建失败,会调用callBadAllocHandler函数,如果不支持快速alloc则调用class_createInstance

我们再看看定义FAST_ALLOC的地方

#if !__LP64__

...

#elif 1
// Leaks-compatible version that steals low bits only.

// class or superclass has .cxx_construct implementation
#define RW_HAS_CXX_CTOR       (1<<18)
// class or superclass has .cxx_destruct implementation
#define RW_HAS_CXX_DTOR       (1<<17)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define RW_HAS_DEFAULT_AWZ    (1<<16)

// class is a Swift class
#define FAST_IS_SWIFT           (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL

#else
..
#define FAST_ALLOC              (1UL<<50)
// instance size in units of 16 bytes
//   or 0 if the instance size is too big in this field
//   This field must be LAST
#define FAST_SHIFTED_SIZE_SHIFT 51

这里有个#elif 1,所以现在其实FAST_ALLOC 是没有定义的,所以一定会调用class_createInstance创建对象。 在objc-runtime-new.mm中可以找到class_createInstance方法

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

从源码可以看出其实真正调用的是_class_createInstanceFromZone

_class_createInstanceFromZone函数过程如下: 1、对 cls 判断 nil,如果 cls 是 nil 的话就直接返回 nil。

2、assert(cls->isRealized());判断该类是否已经做过realize,关于realize的详请可以参考这篇这篇,realize主要是一些数据的拷贝和整理对齐。

3、判断是否支持hasCxxCtorhasCxxDtor还有canAllocNonpointerhasCxxCtorhasCxxDtor是对 Objective-C++ 的支持,表示这个类是否有 C++ 类构造函数和析构函数,如果有的话,需要进行额外的工作。canAllocNonpointer我们不用太关心,这里OC 2.0以上基本上返回的都是true。

4、通过instanceSize获取该对像的大小

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

通过代码我们可以看出来OC中的对象最小大小是16字节。

5、一般zone都是为false(NSZone 已经弃用),fast都是true,所以最后都是调用initInstanceIsa进行初始化

总结

最后我们用一张图概括下alloc的流程

init实现

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

我们发现init其实什么也没做,只是返回了对象本身。这里还有段注释说很多类可能没有调用[super inti],所以这个init其实作用并不是特别大。

SideTable介绍

在介绍retain的实现之前我们先介绍一个跟引用计数相关的数据结构SideTable

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

其中RefcountMap refcnts存放的就是引用计数,slock是同步锁,weak_table是weak table跟ARC的weak实现相关。

引用计数器定义了几个重要的宏,用来存储一些跟引用计数相关的标志位。

// The order of these bits is important.
#ifdef __LP64__
#   define WORD_BITS 64
#else
#   define WORD_BITS 32
#endif

#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED         (1UL<WORD_BITS-1))

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)

每个retainCount都是size_t,是无符号整形 所以根据上面的宏定义,一个retainCount的位区域划分如下图

SIDE_TABLE_WEAKLY_REFERENCED (内存的第 1位)标识该对象是否有过 weak 对象; SIDE_TABLE_DEALLOCATING(内存的第 2 位),标识该对象是否正在 dealloc。 SIDE_TABLE_RC_ONE (内存的第 3 位),存放引用计数数值(三位之后都用来存放引用计数数值)。 所以每次我们引用计数加一时,真正加的是4,在取出真正的引用计数时需要右移两位

retain实现

// Equivalent to calling [this retain], with shortcuts if there is no override
// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}

inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

retain 函数只是直接调用了rootRetainrootRetain首先判断是否为TaggedPointer,之后对 isa 中是否有自定义 retain 和 release,如果没有自定义的实现,则进入sidetable_retain 函数,否则的话直接向对象发送 retain 消息。 判断是否为TaggedPointer,是因为针对TaggedPointer会做一些优化。本文就不详细叙述~我们在下一讲中在进行介绍。

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

sidetable_retain先从SideTables中取出SideTable,然后从table.refcnts中取出自己的retainCount存储区域,refcntStorage += SIDE_TABLE_RC_ONE,而且这里有上锁,所以retain是线程安全的。之所以有SideTables,是为了减小锁的粒度,如果直接存放在一个SideTable中,那这个SideTable就是全局上锁了势必性能不好。

总结

  • SideTable包含着一个自旋锁slock来防止操作时可能出现的多线程读取问题、一个弱引用表weak_table以及引用计数表refcnts。
  • RefcountMap 通过Map的结构存储了对象持有者的地址以及引用计数
  • SideTables中存放SideTable,SideTable中存放refcnts,是两层嵌套。
  • 由于bitMask的使用,每次retain引用计数的值实际上增加了(1 « 2) = 4而不是1

release实现

- (oneway void)release {
    ((id)self)->rootRelease();
}

inline bool 
objc_object::rootRelease()
{
    if (isTaggedPointer()) return false;
    return sidetable_release(true);
}

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

release需要判断最终是否需要调用dealloc,所以会复杂些,流程如下: 1)先遍历 table.refcnts,寻找是否有对应对象的retainCount,如果不存在do_dealloc = true 2)如果存在再判断标志位是否小于SIDE_TABLE_DEALLOCATING(引用计数是否为0,可以发现这个时候我们还没有减1,但已经跟0进行判断了,所以可以发现表中存的引用计数实际上存的是真实的引用计数-1),如果小于do_dealloc = true 3)否则就减去一个SIDE_TABLE_RC_ONE(引用计数-1) 4)最后看do_dealloc是否需要调用dealloc。

三、retainCount、dealloc的实现

retainCount实现

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

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

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

流程如下: 1)声明refcnt_result并且在初始化的时候设为1(用来+1) 2)从SideTables找出对应对象的SideTable。 3)判断refcnts中是否存该对象,如果存在,先将值» SIDE_TABLE_RC_SHIFT得到真实的引用计数值,然后返回引用计数+1

至于» SIDE_TABLE_RC_SHIFT和加1是因为我们上面有提到,数据结构的后两位用了做了别的用途,所以有» SIDE_TABLE_RC_SHIFT。而表中存的值实际上真实的引用计数值-1。

dealloc的实现

- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    object_dispose((id)this);
}

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);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

可以发现,dealloc主要是调用objc_destructInstance方法,objc_destructInstance中做了很多事情,清理关联对象,ARC下释放成员变量等等,这里我们留到下一讲再详细叙述。

参考目录

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:http://melonteam.com复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • Objective-C 内存管理

    1) 手动引用计数 MRC (Mannul Reference Counting);

    meteoric
  • iPhone/Mac Objective-C内存管理教程和原理剖析(一)基本原理

    前言 初学objectice-C的朋友都有一个困惑,总觉得对objective-C的内存管理机制琢磨不透,程序经常内存泄漏或莫名其妙的崩溃。我在这里总结了自己对...

    EltonZheng
  • 聊聊Objective-C内存管理

    内存管理的文章网上太多了,本文只是简单的聊聊内存管理 让你加深内存管理的理解。 了解内存管理首先你需要思考几个问题 1.为什么需要进行内存管理? 2.内管...

    赵哥窟
  • Objective-C内存管理指南

    本文翻译自Advanced Memory Management Programming Guide

    Helloted
  • Objective-C中的内存管理

            在编程语言中是少不了对内存的管理的,内存对于计算机来说是宝贵的资源,所以对使用不到的资源进行回收是很有必要的。OC中使用引用计数和垃圾回收来管理...

    lizelu
  • Objective-C 内存管理(上)学习笔记

    这里的“计数”表明必然会有一个东西(变量)来记录引用的变化,而在OC里这个变量就是retainCount;那么还有一个问题就是通过什么方式来操作这个变量,OC里...

    半纸渊
  • Objective-c内存管理学习总结

    1、 自己生成的对象,自己持有,以alloc、new、copy、mutablecopy开头的方法;

    江中散人_Jun
  • objective-C 的内存管理之-实例分析

    注:这是《Objective-C基础教程》一书上的实例,但是原书限于篇幅,分析得比较简单,初次阅读看得比较费劲,这里展开详细讨论一下。 场景:有二个类Car和E...

    菩提树下的杨过
  • 对象原理探究(一)

    我们要探究一个对象,那么就要找到其属性或者方法等所对应的源码。首先,我来介绍三种探索源码(即定位源码位置)的方式。

    拉维
  • objective-C 的内存管理之-引用计数

    obj-c本质就是"改进过的c语言",大家都知道c语言是没有垃圾回收(GC)机制的(注:虽然obj-c2.0后来增加了GC功能,但是在iphone上不能用,因此...

    菩提树下的杨过
  • Swift 对象内存模型探究(一)

    HandyJSON 是 Swift 处理 JSON 数据的开源库之一,类似 JOSNModel,它可以直接将 JSON 数据转化为类实例在代码中使用。 由于 S...

    腾讯Bugly
  • iPhone/Mac Objective-C内存管理教程和原理剖析(三)@property (retain)和@synthesize的默认实现

    三 @property (retain)和@synthesize的默认实现 在这里解释一下@property (retain) ClassB* objB;和@s...

    EltonZheng
  • Java内存管理原理及内存区域详解

    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包...

    哲洛不闹
  • Java内存管理原理及内存区域详解

    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包...

    哲洛不闹
  • objective-C 的内存管理之-自动释放池(autorelease pool)

    如果一个对象的生命周期显而易见,很容易就知道什么时候该new一个对象,什么时候不再需要使用,这种情况下,直接用手动的retain和release来判定其生死足矣...

    菩提树下的杨过
  • GCD原理探究(一)——创建队列

    1,栈区,由编译器自动分配并释放,在运行的时候分配,用于存储函数的参数、局部变量、指针等。

    拉维
  • 探索OS的内存管理原理

    内存作为计算机系统的组成部分,跟开发人员的日常开发活动有着密切的联系,我们平时遇到的Segment Fault、OutOfMemory、Memory Leak、...

    元闰子
  • Flink 原理与实现:内存管理

    北京理工大学硕士毕业,2015 年加入阿里巴巴,参与阿里巴巴实时计算引擎 JStorm 的开发与设计。2016 年开始从事阿里新一代实时计算引擎 Blink S...

    小晨说数据
  • Spark内部原理之内存管理

    Spark 作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解 Spark 内存管理的基本原理,有助于更好地开发 Spark...

    smartsi

扫码关注腾讯云开发者

领取腾讯云代金券