上一节中咱们探索一下Objc
和LLVM
源码底层关于alloc的完成进程,并了解了怎么来标识记载类的各种状态,总结并解释了提出的相关问题,这节咱们继续看下alloc
后续关于内存拓荒、内存对齐以及类绑定的问题。
一、影响目标内存巨细的因素
在alloc履行后,代码会调用_objc_rootAllocWithZone
->_class_createInstanceFromZone
来进行内存空间的拓荒,咱们在源码中先输出一下目标的内存巨细
和目标在体系中实践拓荒的内存巨细
;
1、LGPerson类没有特点和办法时
初始情况下内存巨细的情况
初始时,默许只要isa占用8个字节
;(目标实践巨细时依照16字节对齐,下面解说),所以输出 8 、8、16;
2、LGPerson类声明四个特点时
特点占用字节别离为1、4、8、8,还有默许的isa 8个字节,依据结构体内存对齐原则(基础知识应该很熟吧),输出别离为8、32、32;
增加特点时,成员变量的巨细和体系拓荒的巨细都增加了
3、LGPerson类特点换成成员变量时
和特点输出是一样的,假如把成员变量的次序换一下
4、LGPerson类中成员变量的次序互换
神奇的一幕出现了,互换成员变量的次序后,成员变量的巨细
和体系拓荒的巨细
都发生了改变,输出变成了8、40、48;
这是由于依据结构体中次序决议了内存对齐
,(而实践的目标巨细是以16字节对齐返回)。
5、LGerson类互换特点的次序并增加两个办法
改动特点次序和增加办法,都没有对内存的巨细产生影响
咱们从上面5种情况咱们可以推断出影响目标内存巨细的因素
是: 特点(或许成员变量)的个数
和成员变量的次序
;那接下来咱们剖析一下源码是怎么完成的。
函数阐明
sizeof
核算数据类型或许指针巨细,p
指针为8
字节;class_getInstanceSize
类中成员变量的巨细,在类结构体class_ro_t中instanceSize
字段来记载(后边详细剖析);malloc_size
核算实践向体系申请拓荒的内存空间巨细,遵循16
字节对齐原则;
小结: 体系底层针对
特点
做了内存对齐的优化,不管特点次序怎么改变,都会依照最优的对齐方式对齐; 而成员变量的对齐并不会进行优化处理;所以日常开发中咱们尽量运用特点,不直接运用成员变量,节约内存;
二、_class_createInstanceFromZone源码剖析
源码如下:
仔细剖析上面源码,总结便是干了3件事:
-
instanceSize
核算成员变量的内存巨细; -
calloc/malloc_zone_calloc
拓荒内存空间巨细(这个空间便是实例目标的空间巨细)(默许履行calloc,由于参数默许传nil); -
initIsa/initInstanceIsa
相关isa和class;(默许履行initInstanceIsa,fast标识是否需求敞开nonpointter,实例目标是isa_t联合体,类目标是isa指针) 接下来咱们来看具体完成:
三、instanceSize内存核算
1、instanceSize
办法
底层代码会读取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
办法
读取_flags
中缓存的成员变量的巨细,【 在上节中咱们说过cache中的_flags
第3-12位来存储instanceSize巨细,最小可存储8字节(1000),最大存储4M(1 0000 0000 0000, 1024*1024 *4字节)】,align16
办法便是16字节对齐算法,所以目标的实践巨细,以16字节对齐返回
。
下图 是类加载时怎么缓存instanceSize巨细:
在realizeClassWithoutSwift办法中
加上父类的instanceSize巨细后,调用setinstanceSize
办法缓存记载;
在reconcileInstanceVariables办法中: 假如父类的成员变量巨细 大于 开始的8个字节(所有目标都有默许的isa开始8个字节),则阐明父类有其它的成员变量,调用moveIvars修正成员变量内存巨细;
核算父类的成员变量巨细,首先要减去开始默许的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库中
咱们在苹果的开发社区看到(malloc下载地址)
最新的源码版本为317.40.8,咱们下载探求一下:
在源码中calloc 其实便是调用的是malloc_zone_calloc
办法,传入了一个默许的default_zone参数(初始化的一个malloc_zone_t结构体);
中心查找进程省掉。。。
直接贴成果函数_nano_malloc_check_clear
在segregated_size_to_fit
对拓荒的空间巨细再次用16字节对齐验证;代码如下:
宏定义NANO_REGIME_QUANTA_SIZE
为16,SHIFT_NANO_QUANTUM
为4,算法公式为(32+(16-1))>> 4 << 4
, 此公式为16字节对齐核算公式。
五、initIsa/initInstanceIsa相关isa
别离看下initInstanceIsa
和initIsa
底层代码
可以看到底层都是调用了initIsa函数
只不过参数不同而已;第一个参数为当时类,第二个参数为isa是否敞开nonpointer(类目标的isa为指针,实例目标的isa为isa_t联合体),第三个参数为是否有默许的.cxx_destruct 析构办法的完成。
初始化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_align
和align16
字节对齐算法,其间以8字节对齐为例,用两种算法公式来阐明:
(size + 7) >> 3 << 3
假定
size
等于6,6 + 7 = 13
==>二进制为0000 1101
>>3
==> 二进制为0000 0001
;<<3
==> 二进制为0000 1000
转换为
10机制
便是8
算法核心是
把二进制的最终三方位为0
。
(size + 7) & ~7
假定
size
等于10
,10 + 7 = 17
,0001 0001
~7
:7
取反,1111 1000
0001 0001
&1111 1000
=0001 0000
转换
10进制
为16
算法核心同样是
把二进制的最终三方位为0
。
总结:
- 目标中成员变量(结构体内部)选用
8
字节对齐;- 目标与目标在堆内存中选用
16
字节对齐;- 体系会对特点进行内存对齐优化,尽量不直接运用成员变量;
- malloc_size获取目标实践拓荒了内存空间巨细;