对象内存分配和占用
准备
分析内存分布之前我们需要解一下x/nuf:
x:代表16进制打印 n:代表打印n个地址单元 u:代表一个地址单元长度(g代表8字节 b代表单字节 h代表双字节 w代表4字节) f:代表显示变量的方式(x按16进制显示 d按10进制显示 u按10进制格式显示无符号整形 o按8进制显示 t按2进制显示 c按字符格式显示 f按浮点数格式显示)
成员变量
@interface WTPerson : NSObject {
@public
short sex;
NSString *name;
NSString *nickName;
int age;
}
@end
2022-04-19 13:24:44.273136+0800 KCObjcBuild[35610:954602] 对象至少需要的内存大小--40
2022-04-19 13:24:44.274207+0800 KCObjcBuild[35610:954602] 系统分配的内存大小--48
由上面的打印结果我们可以看出,对象的内存分配的大小和内存占用大小是不一样的。
对象⾥⾯存储了⼀个isa指针 + 成员变量的值,isa指针是固定的,占8个字节,所以影响对象内存的只有成员变量(属性会⾃动⽣成带下划线的成员变量)
属性
@interface KLPerson : NSObject
//isa -- 48
@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,copy) NSString *hobby;
@property (nonatomic ,assign) int age;
@property (nonatomic ,assign) double hight;
@property (nonatomic ,assign) short number;
@property (nonatomic ,assign) char a;
@end
KLPerson *p = [[LGPerson alloc] init];
p.name = @"Vitus";
p.age = 67;
NSLog(@"最小占用--%lu",class_getInstanceSize([p class]));
NSLog(@"系统分配--%lu",malloc_size((__bridge const void *)(p)));
打印结果
最小占用--32
系统分配--32
从图中我们可以看出第二个内存地址存储来三个值age的0x00000043,sex的0x01,isFree的0x01。这说明实际上系统在对象层面已经对内存对齐进行了优化,不管你属性是怎么样排列的,将数据存储到内存中时都会进行优化处理。
小结
- 内存分布会有对齐操作
- 系统会对属性排列进行优化
- 但是,系统不会对显示声明的成员变量进行优化
isa
isa_t 源码
# if __arm64__
......其他系统
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD
uintptr_t nonpointer : 1; //标识是否为nonpointer
uintptr_t has_assoc : 1; //是否有关联对象
uintptr_t has_cxx_dtor : 1; //该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
uintptr_t shiftcls : 44; // 存储对象的指针
uintptr_t magic : 6; //⽤于调试器判断当前对象是真的对象还是没有初始化的空间
uintptr_t weakly_referenced : 1; //指对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1; //当对象引用计数大于extra_rc所能存储的最大范围时,需借用该变量
uintptr_t extra_rc : 8 //当表示该对象的引⽤计数值,实际上是引⽤计数值减 1
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# endif
| 字段名 | 含义 |
|---|---|
| nonpointer | 表示是否对 isa 指针开启指针优化。0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等 |
| has_assoc | 关联对象标志位,0没有,1存在 |
| has_cxx_dtor | 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。有成员变量就有c++析构函数。 |
| shiftcls | 存储类指针的值。开启指针优化的情况下,在 arm64 架构(真机)中有 33 位用来存储类指针。 |
| magic | 用于调试器判断当前对象是真的对象还是没有初始化的空间 |
| weakly_referenced | 对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。(是否被__weak修饰) |
| deallocating | 标志对象是否正在释放内存 |
| has_sidetable_rc | 是否需要使用 sidetable 来存储引用计数 |
| extra_rc | 表示该对象的引用计数值 |
联合体(union)
联合体⼜叫共⽤体,union就是在内存中划了⼀个⾜够⽤的空间,联合体的成员变量就相当于为这块内存空间开辟了⼏个访问途径,他们共享这⼀块内存。
联合体的⼤⼩计算奉⾏俩个规则:
- 联合体⼤⼩必须能容纳联合体中最⼤的成员变量
- 通过1计算出的联合体⼤⼩必须是联合体中占内存⼤⼩最⼤的基本数据类型⼤⼩的整数倍
共用的内存空间为可以容纳最大的成员变量,且是成员变量最大类型(基本数据类型)的整数倍。
结构体和联合体的区别:结构体中成员变量可以共存,联合体成员变量互斥,节省一定的内存空间
位域
struct LGStruct1 {
char a;
char b;
char c;
char d;
}struct1;
struct LGStruct2 {
// a: 位域名 32:位域长度
int a : 32;
char b : 2;
char c : 7;
char d : 2;
}struct2;
struct LGStruct3 {
// a: 位域名 32:位域长度
char a : 1;
char b : 1;
char c : 1;
char d : 1;
}struct3;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//4,1,4
NSLog(@"%ld,%ld,%ld",sizeof(struct1),sizeof(struct2),sizeof(struct3));
}
return 0;
}
//------
4 8 1
NONPointerIsa
我们可以看到,当nonpointer为0的时候,直接绑定类和地址的对应关系,而当nonpointer为1的时候,除了保存类的信息以外,还会保存一些额外的特殊信息,我们称之为NonpointerIsa。isa其实并不单单是一个指针,以x86_64架构为例,实际上有44位用于存储对象地址。其余位用来存储一些特殊的值。
既然isa中不只有类信息,其中还存在别的特殊信息,那我们怎么屏蔽其他的特殊信息,直接找到类信息呢?
- 第一种方式:使用源码种提供的
ISA_MASK进行掩码0x011d800100008a61 & 0x00007ffffffffff8ULL就可以获取到类名 (isa地址 &ISA_MASK= 类对象地址) - 第二种方式:对
isa指针进行位运算,我们知道对象地址存储在44位上,前面3位和后面17位存储的是特殊的值,那我们只需要清除前面3位和后面17位的值,就能拿到我们需要的类名
小结
nonPointerIsa是isa的优化。保存了引用计数等和对象一些息息相关的东西。不同的架构对应的shiftcls占用的位数不一样。结构体(struct)中所有变量是共存的,⽽联合体(union)中是各变量是互斥的,只能存在⼀个,因为共用内存空间,所有联合体更节省内存空间。