一、内存布局
内核区:4gb内存中只用到了3gb,1gb给内核处理,保留一定的区域给保留区
0XC00000000 = 3221225472 3221225472 / 1024 / 1024 / 1024 = 3gb
0XC00000000 ~ 0X004000000 为五大区的空间
为什么栈区比堆区快呢?
堆区:通过
栈区的指针
找到变量的对象
,再通过变量对象的指针
找到相应的内存空间
;
栈区:直接通过寄存器访问到内存空间
五大区分别是什么内容呢?
栈区:函数,方法以及一些实参,局部变量
堆区:通过alloc分配的对象,block copy
BSS段:未初始化的全局变量,静态变量
数据段:初始化的全局变量,静态变量
text:程序代码,加载到内存中
栈区内存地址:一般为:0x7开头
堆区内存地址:一般为:0x6开头
数据段、BSS内存地址:一般为:0x1开头
面试题:
全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
有区别。
1、定义位置不一样,全局变量定义在相应的全局储存区域,在类外面;而局部变量是在局部的空间。
2、访问的权限也不一样。
当前 Block 是否可以直接修改全局变量?
[UIView animateWithDuration:1 animations:^{
lgname = @"xiaoC";
}];
可以,因为当前全局变量的作用域空间非常的大,它能够起到整个文件的一个效果,所以能够直接访问。
下面有一道 比较坑 的题,大家来看看分别会输出什么?
@implementation CPerson
- (void)run{
personNum ++;
NSLog(@"CPerson内部:%@-%p--%d",self,&personNum,personNum);
}
+ (void)eat{
personNum ++;
NSLog(@"CPerson内部:%@-%p--%d",self,&personNum,personNum);
}
@end
//测试代码
static int personNum = 100;
- (void)test{
NSLog(@"外部运行测试:%p--%d",&personNum,personNum);
personNum = 10000;
NSLog(@"外部运行测试:%p--%d",&personNum,personNum);
[[LGPerson new] run];
NSLog(@"外部运行测试:%p--%d",&personNum,personNum);
[LGPerson eat];
NSLog(@"外部运行测试:%p--%d",&personNum,personNum);
}
现在我们来揭晓答案,看看与你猜测的一不一样。首先前两个应该都没啥问题,分别是100、10000。比较坑的是后面两个:
在[[LGPerson new] run]
时,内部personNum
打印出来是 101,外部是 10000。
在[LGPerson eat]
时,内部personNum
打印出来是 102,外部是 10000。
看到这,是不是比较疑惑呢,personNum
已经是 全局变量了,为什么会出现两个不同的值呢?其实是因为全局变量只针对文件有效
,虽然外部和内部同时用到了,但是 地址指针 是不一样的,所以是两个不同的东西。
那么再来个问题,如果 添加分类 此时执行 [[CPerson alloc] cate_method];
, personNum
的值又会是多少呢?
@implementation CPerson (C)
- (void)cate_method{
NSLog(@"CPerson内部:%@-%p--%d",self,&personNum,personNum);
}
100 ?还是 102 呢?答案是 100,所以一定要牢记,
全局变量只针对文件有效
二、内存管理方案
以下几个是苹果为了进行内存优化,非常典型的几个:
- TaggedPointer:小对象(NSNumber、NSDate)
- NONPOINTER_ISA:非指针型isa
- 散列表:引用计数表,弱引用表
下面我们看一段代码,请问运行代码会发生什么?
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"你的态度,决定你的人生高度!"];
NSLog(@"%@",self.nameStr);
});
}
崩溃,相信有些人猜出来了。那么为什么会崩溃呢?
这里主要考验多线程+setter、getter
,我们在setter
时,要retain newvalue,在getter
时,要release oldvalue,当上个线程的retain、release
还没操作完时,下一个线程就来了,所以当前的setter、getter
都是不安全的,会导致一些访问的是也指针,访问一个空的内存。
那么,如果把代码改成下面这样,还会崩溃嘛?
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"CC"];
NSLog(@"%@",self.nameStr);
});
}
答案是不会,为什么呢???代码没啥不一样啊???
其实这里主要是涉及到TaggedPointer
,我们先来看下retain、release
的源码
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
我们可以看到,源码里
if (obj->isTaggedPointer()) return obj;
这一句,走到这直接return
了,根本不进行retain、release
操作。
看到上面的两个截图,我们可以看到一个
nameStr
是TaggedPointer
,一个是NSCFString
。配合源码,就知道了为什么我们一个会崩溃,一个不会崩溃了。
那么为什么两个nameStr
是不一样的呢???
其实主要是因为我们给
nameStr
赋值不一样导致的,self.nameStr = [NSString stringWithFormat:@"你的态度,决定你的人生高度!"];
这个比较长,不符合小对象的规则(大概在8~10位左右),所以不是TaggedPointer
。
那么TaggedPointer
到底是个什么鬼东西呢?
我们在平常一般是由一个地址,指向当前的变量,但是
TaggedPointer
的地址就不那么简单了,它是由 地址+值 组成的。
NSString *str1 = [NSString stringWithFormat:@"a"];
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"str1:%p-%@",str1,str1);
NSLog(@"str2:%p-%@",str2,str2);
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
NSNumber *number1 = @1;
NSNumber *number2 = @1;
NSNumber *number3 = @2.0;
NSNumber *number4 = @3.2;
NSLog(@"number1:%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"number2:0x%lx",_objc_decodeTaggedPointer_(number2));
NSLog(@"number3:0x%lx",_objc_decodeTaggedPointer_(number3));
NSLog(@"number4:0x%lx",_objc_decodeTaggedPointer_(number4));
uintptr_t
_objc_decodeTaggedPointer_(id ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
打印信息:
2020-03-20 14:19:41.431738+0800 taggedpointer[10578:698680] str1:0x9624d75def409be0-a
2020-03-20 14:19:41.431830+0800 taggedpointer[10578:698680] str2:0x9624d75def409bd0-b
2020-03-20 14:19:41.431913+0800 taggedpointer[10578:698680] 0xa000000000000621
2020-03-20 14:19:41.432031+0800 taggedpointer[10578:698680] number1:__NSCFNumber-0x8624d75def409de3-1 - 0xb000000000000012
2020-03-20 14:19:41.432105+0800 taggedpointer[10578:698680] number2:0xb000000000000012
2020-03-20 14:19:41.432191+0800 taggedpointer[10578:698680] number3:0xb000000000000025
2020-03-20 14:19:41.432267+0800 taggedpointer[10578:698680] number4:0x3624b75ded798f91
我们看到
str2:0x9624d75def409bd0-b
中0x9624d75def409be0
这个很奇怪,与我们平常见到的不一样。那么我们来看看源码initializeTaggedPointerObfuscator();
,看看它做了什么样的骚操作。
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
我们可以看到,在iOS 10.14之前
objc_debug_taggedpointer_obfuscator = 0
,之后是objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
extern uintptr_t objc_debug_taggedpointer_obfuscator;
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
/**
1000 0001
^0001 1000 异或:不同为1,相同为0
1001 1001
^0001 1000
1000 0001
*/
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
我们可以看到当一个对象传进来一个地址,我们得对它进行异或运算。所以要想看到地址
0x9624d75def409be0
的真实面目,我们要进行_objc_decodeTaggedPointer
,得到0xa000000000000621
,那么0xa
代表的是什么呢?
a = 1010(二进制),第一个1表示是TaggedPointer
,010=2(十进制),因为OBJC_TAG_NSString = 2,
,所以0xa
表示这个是TaggedPointer
中的NSString
类型。
0xa000000000000621
中的62
是 ASCLL 中的b
。至于0xa000000000000621
的最后一位:
- 0 表示unsigned int类型,
- 1 表示short类型,
- 2 表示整型,
- 3 表示长整型,
- 4 表示单精度类型,
- 5 表示双精度类型。
小结:
Tagged Pointer专⻔用来存储小的对象,例如NSNumber和NSDate
Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再 是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储 在堆中,也不需要
malloc
和free
在内存读取上有着3倍的效率,创建时比以前快106倍。