内存管理(一)

一、内存布局

内核区:4gb内存中只用到了3gb,1gb给内核处理,保留一定的区域给保留区

0XC00000000 = 3221225472 3221225472 / 1024 / 1024 / 1024 = 3gb
0XC00000000 ~ 0X004000000 为五大区的空间

为什么栈区比堆区快呢?

堆区:通过栈区的指针找到变量的对象,再通过变量对象的指针找到相应的内存空间
栈区:直接通过寄存器访问到内存空间

五大区分别是什么内容呢?

栈区:函数,方法以及一些实参,局部变量
堆区:通过alloc分配的对象,block copy
BSS段:未初始化的全局变量,静态变量
数据段:初始化的全局变量,静态变量
text:程序代码,加载到内存中

栈区内存地址:一般为:0x7开头
堆区内存地址:一般为:0x6开头
数据段、BSS内存地址:一般为:0x1开头


栈区和堆区的内存地址.png

面试题:
全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

有区别。
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 已经是 全局变量了,为什么会出现两个不同的值呢?其实是因为 全局变量只针对文件有效,虽然外部和内部同时用到了,但是 地址指针 是不一样的,所以是两个不同的东西。

文件不同,全局变量的地址指针不同.png

那么再来个问题,如果 添加分类 此时执行 [[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操作。

非TaggedPointer.png
TaggedPointer.png

看到上面的两个截图,我们可以看到一个nameStrTaggedPointer,一个是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-b0x9624d75def409be0 这个很奇怪,与我们平常见到的不一样。那么我们来看看源码 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中的62ASCLL 中的b。至于0xa000000000000621的最后一位:

  • 0 表示unsigned int类型,
  • 1 表示short类型,
  • 2 表示整型,
  • 3 表示长整型,
  • 4 表示单精度类型,
  • 5 表示双精度类型。

小结:

  • Tagged Pointer专⻔用来存储小的对象,例如NSNumber和NSDate

  • Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再 是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储 在堆中,也不需要 mallocfree

  • 在内存读取上有着3倍的效率,创建时比以前快106倍。

你可能感兴趣的:(内存管理(一))