一、获取内存大小的三种方式
1.1 sizeOf
sizeof是一个操作符,不是函数sizeof计算内存大小时,传入的主要是对象或者数据类型,在编译器的编译阶段大小就已经确定sizeof计算出的结果,是表示占用空间的大小
1.2 class_getInstanceSize
class_getInstanceSizeruntime 提供的api,用于获取类实例对象所占内存大小,本质也是计算该实例中所有成员变量的内存大小
1.3 malloc_size
malloc_size获取系统实际分配的内存大小
1.4 code示例
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
LGPerson *p = [LGPerson alloc];
NSLog(@"p对象类型占用的内存大小:%lu",sizeof(p));
NSLog(@"p对象实际占用的内存大小:%lu",class_getInstanceSize([p class]));
NSLog(@"p对象实际分配的内存大小:%lu",malloc_size((__bridge const void*)(p)));
return 0;
}
打印结果如下:
总结
sizeof打印的p为对象指针地址一个指针的大小为8字节class_getInstanceSize打印的是内部成员变量的大小isa占8字节name字符串8字节ageint类型4字节8+8+4=20因为类的成员变量按照8字节对齐规则20->24malloc_size为系统实际分配的大小,分配给对象的大小是按照16字节对齐,16字节对齐的算法 简单理解为24+(16 -1 )>>4 <<4得到32
二、内存对齐规则
- 【原则一】 数据成员的对齐规则可以理解为
min(m, n)的公式, 其中 m表示当前成员的开始位置,n表示当前成员所需要的位数。如果满足条件m整除n(即m % n == 0),n从m位置开始存储, 反之继续检查m+1能否整除n, 直到可以整除, 从而就确定了当前成员的开始位置。 - 【原则二】数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体
a嵌套结构体b,b中有char、int、double等,则b的自身长度为8 - 【原则三】最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。
三、验证规则
1.1 各数据类型所占字节大小
1.2 创建两个不同的结构体 myStuct1,myStruct2 分别进行验证 code如下
struct myStruct1 {
char a; // 1
double b; //8
int c; //4
short d; //2
}myStruct1;
struct myStruct2 {
double b;//8
int c;//4
short d;//2
char a;//1
}myStruct2;
NSLog(@"%lu-%lu",sizeof(myStruct1),sizeof(myStruct2));
打印结果如下:24-16
结果分析:两个结构体的 成员变量是一致的,唯一不同的是成员变量的摆放顺序不同,位置不同导致所占内存大小不同,这就是内存对齐,我们按照内存对齐规则进行分析:
分析结构体myStruct1
- 【1】
a占1字节 从位置0开始0%1 == 00的位置存a - 【2】
b占8字节 从位置1开始1%8不等于0移到88%8==08-15存b - 【3】
c占4字节 从位置16开始16%4等于016-19存c - 【4】
d占2字节 从位置20开始20%2等于020-21存d - 【5】规则三,内存大小必须为结构体中最大成员
8的整数倍,22变成8的倍数 变成24
分析结构体myStruct2
- 【1】
b占8字节 从位置0开始0%1 == 07的位置存b - 【2】
c占4字节 从位置8开始8%4等于08-11存c - 【3】
d占2字节 从位置12开始12%4等于012-13存d - 【4】
a占1字节 从位置14开始14%1等于014存a - 【5】规则三,内存大小必须为结构体中最大成员
8的整数倍,14变成8的整数倍 变成16
1.3 新增一个结构体myStruct3 嵌套myStrct2 code如下
struct myStruct2 {
double b;//8
int c;//4
short d;//2
char a;//1
}myStruct2;
struct myStruct3 {
double b;//8
int c;//4
short d;//2
char a;//1
struct myStruct2 str;
}myStruct3;
NSLog(@"%lu",sizeof(myStruct3));
打印结果如下:32
分析结构体myStruct3
- 【1】
b占8字节 从位置0开始0%1 == 07的位置存b - 【2】
c占4字节 从位置8开始8%4等于08-11存c - 【3】
d占2字节 从位置12开始12%4等于012-13存d - 【4】
a占1字节 从位置14开始14%1等于014存a - 【5】
str是一个结构体,规则二 结构体成员要从其内部最大成员大小的整数倍开始存储,而myStruct2中最大的成员大小为8所以str要从8的整数倍开始,当前是从15开始 所以不符合要求,需要往后移动到16,16是8的整数倍,符合内存对齐原则 所以16-31存str - 【5】规则三,内存大小必须为结构体中最大成员
8的整数倍,32刚刚满足
四、内存优化
思考内存优化
接着上面的结论,我们思考下,为什么要内存对齐,其实苹果这么做的目的让cpu读取内存的效率更高,用空间换取时间 我们创建一个类LGPerson创建一些属性,并赋值查看内存摆放是什么样的?code如下:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic,assign)int height;
@end
LGPerson *p = [LGPerson alloc];
p.name=@"lb";
p.nickName=@"LB";
p.age=18;
p.height=180;
断点查看p对象的内存地址
按照内存对齐原则,第一个为isa,第二个为name,第三个为age,后面按顺序摆放,通过
po 0x000000b400000012打印发现0x000000b400000012打印出来不是lb而0x0000000100004010打印出来为lbpo 0x0000000100004030为LB那age 和height去哪了 看到0x000000b400000012分开可以看出0x000000b4等于1800x00000012等于18可以看出系统进行了优化,属性进行了重排
总结
所以,这里可以总结下苹果中的内存对齐思想: 大部分的内存都是通过固定的内存块进行读取, 尽管我们在内存中采用了内存对齐的方式,但并不是所有的内存都可以进行浪费的,苹果会自动对属性进行重排,以此来优化内存
五、为什么分配内存大于实际占用内存
通过上面的探索,我们知道对象的内存是根据你成员变量内存大小通过
8字节对齐来计算的 但是实际内存分配大小不是按照8字节来的,是按照16字节对齐来的 我们可以通过objc源码来探究 code如下
通过 size = cls->instanceSize(extraBytes); //计算大小
//alignedInstanceSize() + extraBytes; //内存对齐方法
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
//x+7 & (~7) --> 8字节对齐
return (x + WORD_MASK) & ~WORD_MASK;
}
# define WORD_MASK 7UL
通过源码可以知道,计算内存大小实际是按照8字节对齐计算的
class_getInstanceSize:是采用8字节对齐,参照的对象的属性内存大小malloc_size:采用16字节对齐,参照的整个对象的内存大小,对象实际分配的内存大小必须是16的整数倍
总结:
- 对象大小和成员变量的类型和数量有关
- 对象实际内存是按照8字节对齐
- 对象分配内存大小按照16字节对齐。
- 16字节对齐方法,内存占用大小x x+15 >>4 <<4 得到内存分配大小