JHHK

欢迎来到我的个人网站
行者常至 为者常成

NSObject的本质(二) 内存查看与优化

目录

NSObject对象内存查看

//创建一个Student对象
Student * student = [[Student alloc] init];
student->_age = 18;
student->_number = 20;

lldb调试:

(lldb) po class_getInstanceSize([student class])    
16

(lldb) po malloc_size((__bridge const void*)student);     
16

(lldb) po student      
<Student: 0x10070c370>

可知申请内存大小为16,分配内存大小为16。起始地址为0x10070c370,所以具体的内存分配如下:

struct Student_IMPL{    
    Class isa;  //8字节,成员地址:0x10070c370    
    int age;    //4字节,成员地址:0x10070c378    
    int number; //4字节,成员地址:0x10070c37c    
}    

isa

(lldb) memory read/1xg 0x10070c370     
0x10070c370: 0x001d800100001309  

isa指针指向的是类对象,后续文章会详细说明。到要得到类对象的地址需要 0x001d800100001309 & MASk 进行运算。
因为,isa 指针要通过 MASK 处理之后才能对应到具体的值。所以先用po指令打印下:

(lldb) po 0x10070c370     
<Student: 0x10070c370>    

Student就是类对象。<Student: 0x10070c370>表示Student类对象的一个实例对象,对象的内存地址是0x10070c370。

age

(lldb) memory read/1dw 0x10070c378     
0x10070c378: 18   

number

(lldb) memory read/1dw 0x10070c37c    
0x10070c37c: 20 

NSObject对象内存优化

1.定义如下结构体:

struct Student_IMPL{
    int64_t isa;//用int64_t 代替Class
    char _ch1;
    long _height;
    char _ch2;
    int _age;
};

struct Student_IMPL stu;
stu.isa = 100;
stu._ch1 = 'a';
stu._height = 185;
stu._ch2 = 'b';
stu._age = 18;

lldb调试:
查看大小

(lldb) p sizeof(stu)
(unsigned long) $6 = 32

查看结构体地址:

(lldb) p &stu
(Student_IMPL *) $0 = 0x00007ffeefbff5b0

查看各成员的地址:可以看出满足内存对齐

(lldb) p &stu.isa
(int64_t *) $1 = 0x00007ffeefbff5b0

(lldb) p &stu._ch1
(char *) $2 = 0x00007ffeefbff5b8 "a"

(lldb) p &stu._height
(long *) $3 = 0x00007ffeefbff5c0

(lldb) p &stu._ch2
(char *) $4 = 0x00007ffeefbff5c8 "b"

(lldb) p &stu._age
(int *) $5 = 0x00007ffeefbff5cc

内存分配如下表所示

成员变量 占用地址 大小(字节)
isa 0x00007ffeefbff5b0~0x00007ffeefbff5b7 8
_ch1 0x00007ffeefbff5b8 1
对齐 0x00007ffeefbff5b9~0x00007ffeefbff5bf 7
_height 0x00007ffeefbff5c0~0x00007ffeefbff5c7 8
_ch2 0x00007ffeefbff5c8~0x00007ffeefbff5cb 4
_age 0x00007ffeefbff5cc~0x00007ffeefbff5cf 4

占用的地址空间为:0x00007ffeefbff5b0~0x00007ffeefbff5cf,共32个字节。

2.创建一个Student对象与Student_IMPL一一对应,我们来看下它的大小及内存分配情况

//Student声明
@interface Student : NSObject
@property (nonatomic, assign) char      ch1;     // 1个字节
@property (nonatomic, assign) long      height;  // 8个字节
@property (nonatomic, assign) char      ch2;     // 1个字节
@property (nonatomic, assign) int       age;     // 4个字节
@end

//创建一个Student对象
Student * obj = [[Student alloc] init];
// 对象创建赋值
obj.ch1 = 'a';
obj.height = 185;
obj.ch2 = 'b';
obj.age = 18;

lldb调试:
查看大小

(lldb) po class_getInstanceSize([obj class]);
24

那么与结构体相同的数据组成,为什么占用的空间大小一个是32,而一个却是24呢?,下面我们来看下Student实例的内存是如何分配的。

查看地址

(lldb) po obj
<Student: 0x28103a160>

查看实际分配的内存大小:

(lldb) po malloc_size((__bridge const void *)obj);
32

那么obj对象占用的内存空间为:0x28103a160 ~ 0x28103a17f。
下面来看看这段内存内实际存放的数据是什么

(lldb) p &obj->isa
(Class *) $10 = 0x000000028103a160

(lldb) p &obj->_ch1
(char *) $11 = 0x000000028103a168 "ab"

(lldb) p &obj->_height
(long *) $12 = 0x000000028103a170

(lldb) p &obj->_ch2
(char *) $13 = 0x000000028103a169 "b"

(lldb) p &obj->_age
(int *) $14 = 0x000000028103a16c

(lldb) memory read/4xg 0x28103a160
0x28103a160: 0x000001a102a4dd39 0x0000001200006261
0x28103a170: 0x00000000000000b9 0x0000000000000000

内存分配如下图所示

成员变量 地址 大小(字节)
isa 0x000000028103a160 ~ 0x000000028103a167 8
_ch1 0x000000028103a168 1
_ch2 0x000000028103a169 1
对齐 0x000000028103a16a ~ 0x000000028103a16b 2
_age 0x000000028103a16c ~ 0x000000028103a16f 4
_height 0x000000028103a170 ~ 0x000000028103a177 8
对齐 0x000000028103a178 ~ 0x000000028103a17f 8

实际需要的内存为:0x28103a160 ~ 0x28103a177,共24个字节 实际分配的内存为:0x28103a160 ~ 0x28103a17f,共32个字节

可以看出成员变量并没有按照书写顺序排放,而是进行了优化。
实际需要的内存空间为:8+1+1+2+4+8=24.
实际分配时因为需要是16的倍数,所以最后补齐8字节,实际分配的空间为:8+1+1+2+4+8+8=32

从这里可以得出,在类的成员变量的内存大小分配的时候;系统会进行一次优化,将能进行对齐优化的成员变量放在一个段里面,节约内存空间,以时间换空间,加快 CPU 读取字节时候的偏移计算复杂度;而不是根据成员声明定义的顺序依次进行排布,这里就调整了顺序,然后直接最大化优化内存字节对齐。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.