上一节中咱们探索一下ObjcLLVM源码底层关于alloc的完成进程,并了解了怎么来标识记载类的各种状态,总结并解释了提出的相关问题,这节咱们继续看下alloc后续关于内存拓荒、内存对齐以及类绑定的问题。

一、影响目标内存巨细的因素

在alloc履行后,代码会调用_objc_rootAllocWithZone->_class_createInstanceFromZone来进行内存空间的拓荒,咱们在源码中先输出一下目标的内存巨细和目标在体系中实践拓荒的内存巨细

1、LGPerson类没有特点和办法时

初始情况下内存巨细的情况

OC对象底层内存开辟和实现(下)
初始时,默许只要isa占用8个字节;(目标实践巨细时依照16字节对齐,下面解说),所以输出 8 、8、16;

2、LGPerson类声明四个特点时

OC对象底层内存开辟和实现(下)
OC对象底层内存开辟和实现(下)
特点占用字节别离为1、4、8、8,还有默许的isa 8个字节,依据结构体内存对齐原则(基础知识应该很熟吧),输出别离为8、32、32;

增加特点时,成员变量的巨细和体系拓荒的巨细都增加了

3、LGPerson类特点换成成员变量时

OC对象底层内存开辟和实现(下)
OC对象底层内存开辟和实现(下)
和特点输出是一样的,假如把成员变量的次序换一下

4、LGPerson类中成员变量的次序互换

OC对象底层内存开辟和实现(下)
OC对象底层内存开辟和实现(下)
神奇的一幕出现了,互换成员变量的次序后,成员变量的巨细体系拓荒的巨细都发生了改变,输出变成了8、40、48;

这是由于依据结构体中次序决议了内存对齐,(而实践的目标巨细是以16字节对齐返回)。

5、LGerson类互换特点的次序并增加两个办法

OC对象底层内存开辟和实现(下)
OC对象底层内存开辟和实现(下)
改动特点次序和增加办法,都没有对内存的巨细产生影响

咱们从上面5种情况咱们可以推断出影响目标内存巨细的因素是: 特点(或许成员变量)的个数成员变量的次序;那接下来咱们剖析一下源码是怎么完成的。

函数阐明

  • sizeof 核算数据类型或许指针巨细,p指针为8字节;
  • class_getInstanceSize类中成员变量的巨细,在类结构体class_ro_t中instanceSize字段来记载(后边详细剖析);
  • malloc_size核算实践向体系申请拓荒的内存空间巨细,遵循16字节对齐原则;

小结: 体系底层针对特点做了内存对齐的优化,不管特点次序怎么改变,都会依照最优的对齐方式对齐; 而成员变量的对齐并不会进行优化处理;

所以日常开发中咱们尽量运用特点,不直接运用成员变量,节约内存;

二、_class_createInstanceFromZone源码剖析

源码如下:

OC对象底层内存开辟和实现(下)
仔细剖析上面源码,总结便是干了3件事:

  1. instanceSize核算成员变量的内存巨细;
  2. calloc/malloc_zone_calloc 拓荒内存空间巨细(这个空间便是实例目标的空间巨细)(默许履行calloc,由于参数默许传nil);
  3. initIsa/initInstanceIsa相关isa和class;(默许履行initInstanceIsa,fast标识是否需求敞开nonpointter,实例目标是isa_t联合体,类目标是isa指针) 接下来咱们来看具体完成:

三、instanceSize内存核算

1、instanceSize办法

OC对象底层内存开辟和实现(下)
底层代码会读取cache(缓存)中的fastInstaceSize巨细(存储在cache的_flags中,在上节中咱们说过cache中的_flags第3-12位来存储instanceSize巨细);

由于在编译阶段成员变量巨细现已确定,存放在MachO文件中;当类加载时,会在realizeClassWithoutSwift->reconcileInstanceVariables核算父类的成员变量的巨细,并修正class_ro_t中的开始巨细(默许isa指针巨细)和instanceSize巨细;

在老版本的源代码中,是没有缓存的,在instanceSize办法中履行的是 读取缓存下面的代码。

size_t size = alignedInstanceSize() + extraBytes;

if (size < 16) size = 16;

return size; alignedInstanceSize()办法中word_align办法是8字节对齐算法(如上图标示部分);

2、cache.fastInstaceSize办法

OC对象底层内存开辟和实现(下)
读取_flags中缓存的成员变量的巨细,【 在上节中咱们说过cache中的_flags第3-12位来存储instanceSize巨细,最小可存储8字节(1000),最大存储4M(1 0000 0000 0000, 1024*1024 *4字节)】,align16办法便是16字节对齐算法,所以目标的实践巨细,以16字节对齐返回

下图 是类加载时怎么缓存instanceSize巨细:

realizeClassWithoutSwift办法中

OC对象底层内存开辟和实现(下)
加上父类的instanceSize巨细后,调用setinstanceSize办法缓存记载;

reconcileInstanceVariables办法中:

OC对象底层内存开辟和实现(下)
假如父类的成员变量巨细 大于 开始的8个字节(所有目标都有默许的isa开始8个字节),则阐明父类有其它的成员变量,调用moveIvars修正成员变量内存巨细;

OC对象底层内存开辟和实现(下)
核算父类的成员变量巨细,首先要减去开始默许的isa的字节(所有目标都有默许的isa开始8个字节);然后再8字节对齐;最终修正开始字节数和instanceSize巨细;

小结:

在获取instanceSize巨细时,成员变量巨细(排序有关)都是以8字节对齐的;

而实践返回的目标巨细是以16字节对齐的;

一个目标的占用内存巨细 最小为8字节,最大为4M;

关于字节对齐word_align和align16算法???

四、calloc/malloc_zone_calloc内存拓荒

id obj;

if (zone) {

obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);

} else {

obj = (id)calloc(1, size);

}

在拓荒内存空间时,size巨细便是返回目标的实践巨细(以16字节对齐),咱们通过跳转定义看到calloc/malloc_zone_calloc源码在malloc库中

OC对象底层内存开辟和实现(下)
咱们在苹果的开发社区看到(malloc下载地址)

OC对象底层内存开辟和实现(下)
最新的源码版本为317.40.8,咱们下载探求一下:
OC对象底层内存开辟和实现(下)
在源码中calloc 其实便是调用的是malloc_zone_calloc办法,传入了一个默许的default_zone参数(初始化的一个malloc_zone_t结构体);

OC对象底层内存开辟和实现(下)
中心查找进程省掉。。。 直接贴成果函数_nano_malloc_check_clear
OC对象底层内存开辟和实现(下)
segregated_size_to_fit对拓荒的空间巨细再次用16字节对齐验证;代码如下:
OC对象底层内存开辟和实现(下)
宏定义NANO_REGIME_QUANTA_SIZE为16,SHIFT_NANO_QUANTUM为4,算法公式为(32+(16-1))>> 4 << 4, 此公式为16字节对齐核算公式。

五、initIsa/initInstanceIsa相关isa

别离看下initInstanceIsainitIsa底层代码

OC对象底层内存开辟和实现(下)
OC对象底层内存开辟和实现(下)
可以看到底层都是调用了initIsa函数只不过参数不同而已;第一个参数为当时类,第二个参数为isa是否敞开nonpointer(类目标的isa为指针,实例目标的isa为isa_t联合体),第三个参数为是否有默许的.cxx_destruct 析构办法的完成。

OC对象底层内存开辟和实现(下)
初始化isa,当nonpointer为flase时,阐明不需求敞开nonpointer优化,那么isa指针直接指向了class类;

当nonpointer为true时,阐明需求敞开nonpointer优化,那么给isa_t位域赋值并指向class类;然后给objc_object(目标)中的isa赋值绑定;

最终拓荒的空间目标obj指向了初始化的目标,完成和类的相关;

六、疑问解答

1、class_getInstanceSize函数

源码如下:

size_t class_getInstanceSize(Class cls)

{

if (!cls) return 0;

return cls->alignedInstanceSize();

}

uint32_t alignedInstanceSize() const {

return word_align(unalignedInstanceSize());

}

uint32_t unalignedInstanceSize() const {

ASSERT(isRealized());

return data()->ro()->instanceSize;

}

static inline uint32_t word_align(uint32_t x) {

return (x + WORD_MASK) & ~WORD_MASK;

}

看到这个 alignedInstanceSize办法有没有很熟悉,办法中word_align办法是对获取的instanceSize巨细以8字节对齐;获取的是目标中成员变量占用的内存巨细,假如此时内存巨细刚好也是16字节对齐,那么获取的instanceSize也是目标的巨细。

2、字节对齐

word_alignalign16字节对齐算法,其间以8字节对齐为例,用两种算法公式来阐明:

  1. (size + 7) >> 3 << 3

假定size等于6, 6 + 7 = 13 ==>二进制为 0000 1101

>>3 ==> 二进制为 0000 0001<<3 ==> 二进制为 0000 1000

转换为10机制便是 8

算法核心是 把二进制的最终三方位为0

  1. (size + 7) & ~7

假定size等于1010 + 7 = 170001 0001

~77取反,1111 1000

0001 0001 & 1111 1000 = 0001 0000

转换10进制16

算法核心同样是 把二进制的最终三方位为0

总结:

  • 目标中成员变量(结构体内部)选用8字节对齐;
  • 目标与目标在堆内存中选用16字节对齐;
  • 体系会对特点进行内存对齐优化,尽量不直接运用成员变量;
  • malloc_size获取目标实践拓荒了内存空间巨细;