底层学习05_内存管理(上)

231 阅读6分钟

内存布局

  • 内核区 Oxc0000000
  • 栈区 stack 向下
    • 常规内存512KB 8个字节 65536个指针变量
  • 堆区 heap 向上
  • 未初始化数据 .bss
  • 已初始化数据 .data
  • 代码段 .text
  • 保留

这里引用一下Cooci老师的图

- (void)memories {
    
    int a = 10;
    NSLog(@"%p",&a); // 栈 -- 0x7 栈
    
    static int x = 4;
    NSLog(@"%p",&x); // 静态/敞亮 -- 0x1 开头
    
    NSArray *b = @[@"my", @"name", @"is"];
    NSLog(@"b: %@-%p", b, &b); // 0x6 堆   对象在堆  指针在栈
    NSLog(@"b[0]: %@-%p", b[0], b[0]); // b[0]是常亮0x1开头
    NSObject *obj = [NSObject new]; // 对象 --
    NSLog(@"%@ - %p", obj, &obj); // 0x6 堆   对象在堆  指针在栈
    NSArray *array = [NSArray arrayWithObject:@1];
    NSLog(@"%@-%p", array, &array);
}

运行结果:

2020-05-29 14:51:10.949423+0800 TheMemoryStudy[33080:4559849] 0x7ffee151f0cc
2020-05-29 14:51:10.949592+0800 TheMemoryStudy[33080:4559849] 0x10e6e24f8
2020-05-29 14:51:10.949727+0800 TheMemoryStudy[33080:4559849] b: (
    my,
    name,
    is
)-0x7ffee151f0c0
2020-05-29 14:51:10.949842+0800 TheMemoryStudy[33080:4559849] b[0]: my-0x10e6e0040
2020-05-29 14:51:10.949979+0800 TheMemoryStudy[33080:4559849] <NSObject: 0x60000386c6e0> - 0x7ffee151f0b8
(lldb) po array
<__NSSingleObjectArrayI 0x600003860960>(
1
)

2020-05-29 14:51:37.853619+0800 TheMemoryStudy[33080:4559849] (
    1
)-0x7ffee151f0b0

  • 静态变量
// 一般是放在.m里的, 编译时候放在已初始化数据里.
// 如果放在.h里的话 在其他地方改变值时候,会把personNum拷贝到内存一份然后修正其值
static int personNum = 10;
  • 堆栈溢出,表示堆区和栈区碰撞了.
  • 尽量封装方法,函数. 加快编译速度. 因为一个方法越长,更加难被识别,所以编译速度更慢. -- 用空间换时间.

内存管理方案

  • 先说异或操作 ^ // 相同为0 不同为1
a =        1010 0001
b =        0000 1101
a = a^b => 1010 1100
b = a^b => 1010 0001
a = a^b => 0000 1101
最后a和b互换了. 
a = a^b^b
  • TaggedPointer源码
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.
    // They are reversed here for payload insertion.

    // ASSERT(_objc_taggedPointersEnabled());
    if (tag <= OBJC_TAG_Last60BitPayload) {
        // ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {
        // ASSERT(tag >= OBJC_TAG_First52BitPayload);
        // ASSERT(tag <= OBJC_TAG_Last52BitPayload);
        // ASSERT(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    }
}
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;
}

makeTaggedPointer方法最后,return的_objc_encodeTaggedPointer就是一个异或操作后的值. 取值时候再异或一次就出来原来的值了.

那么TaggedPointer到底是什么呢? TaggedPointer是用来存储小对象用的一种数据结构.

Tagged pointers allow certain classes with small amounts of per-instance data to be stored entirely within the pointer. This can eliminate the need for memory allocations for many uses of classes like NSNumber, and can make for a good performance boost.

简单来说
NSObject *obj = [NSObject new];
地址->值
而TaggedPointer里不光有地址,还存了真正的值. 地址+值
所以taggedPointer创建和读取的数据非常快.

一般来说 NSNumber, 短的NSString等等... 而且taggedPointer是不经过retain和release的.

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}


__attribute__((aligned(16), flatten, noinline))
void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

散列表(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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
  • 结构:

    • SideTable里有:
      • Spinlock_t 锁
      • RefcountMap 引用计数表
      • weak_table_t 弱引用表
    • SideTables里有很多个sideTable(哈希表里存了很多个哈希表)
      • 为什么不用一张SideTable呢?
      • 因为Sidetable里是有引用计数的操作的. 在这一过程中会有锁的操作(散列表里的Spinlock_t). 如果只用一张表的话频繁操作对性能就有影响了. 而使用好多个Sidetable,可以将锁分离.
  • SideTable的读取:

    • 先通过数组SideTables[obj], key是obj获取value. 也就是在SideTables里获取SideTable
  • 弱引用表weak_table_t

这里引用一下Cooci老师的图

  • 弱引用源码:
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
static id 
storeWeak(id *location, objc_object *newObj)
{
    // ... 省略

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    // ... 省略
    
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}
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];
}

解析:

Sidetables获取sidetable
sidetable获取weakTable 
// weak_register_no_lock方法里
weakTable获取weak_entries  
weak_entries获取entry     // weak_entry_for_referent 拿entry
拿到了就给entry {
	referrers,
	num_refs,
	inline_referrers
} 赋值  // 
没位置就扩容(到 3/4 就扩容)  // weak_grow_maybe
然后加进去                 // weak_entry_insert
  • dealloc 源码
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里做的操作:

* 有析构函数,执行析构函数
* 有associated对象, 清理关联对象
* clearDeallocating清理弱引用对象
	* referrer = nil
	* weak_entry_remove(weak_table, entry)
	* table.refcnts.erase(this)
  • strong
void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

这里没什么可说的, 持有新的obj,释放旧的obj

总结

  • 内存布局从高到底一次是内核,栈,堆,数据,代码.
  • NSNumber,短的NSString等对象是TaggedPointer的,存的是地址+值.为了快速和节省内存.
  • taggedPointer不做引用计数的加减.
  • 引用计数是分两个地方,存储在extra_rc和sidetable里的.
  • 弱引用的添加. 类似于@synthesizer锁里的SyncData这种拉链表.找到对应位置,加进去,到3/4就扩容然后加进去.
  • dealloc方法做的事情:有析构函数,执行析构函数.有关联对象,处理关联对象.清理弱引用对象
  • 强引用里做的事情: 持有新对象.赋值.释放旧对象.