内存布局中有七个区,分别是
内核区
、堆区
、栈区
、未初始化数据(静态区)
、已初始化数据(常量区)
、代码段
、保留区
。 c++![]()
下面用代码来探索一下平常使用的数据存放在哪一个区程序员
指针
,以及一些简单的基本数据类型
存储在栈区
。通常来讲,地址为0x7开头
的通常都是在栈区
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"************栈区************");
// 栈区
int a = 10;
int b = 20;
NSObject *object = [NSObject new];
NSLog(@"a == \t%p",&a);
NSLog(@"b == \t%p",&b);
NSLog(@"object == \t%p",&object);
NSLog(@"%lu",sizeof(&object));
NSLog(@"%lu",sizeof(a));
}
@end
复制代码
打印:算法
2020-02-13 16:33:11.605082+0800 001---五大区Demo[1545:519291] ************栈区************
2020-02-13 16:33:11.605169+0800 001---五大区Demo[1545:519291] a == 0x7ffee0cd87fc
2020-02-13 16:33:11.605236+0800 001---五大区Demo[1545:519291] b == 0x7ffee0cd87f8
2020-02-13 16:33:11.605298+0800 001---五大区Demo[1545:519291] object == 0x7ffee0cd87f0
2020-02-13 16:33:11.605357+0800 001---五大区Demo[1545:519291] 8
2020-02-13 16:33:11.605411+0800 001---五大区Demo[1545:519291] 4
复制代码
堆区
通常用来储存new出来的对象
,通常来讲,堆区
的地址通常为0x6开头
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"************堆区************");
// 堆区
NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
NSObject *object3 = [NSObject new];
NSObject *object4 = [NSObject new];
NSObject *object5 = [NSObject new];
NSObject *object6 = [NSObject new];
NSObject *object7 = [NSObject new];
NSObject *object8 = [NSObject new];
NSObject *object9 = [NSObject new];
NSLog(@"object1 = %@",object1);
NSLog(@"object2 = %@",object2);
NSLog(@"object3 = %@",object3);
NSLog(@"object4 = %@",object4);
NSLog(@"object5 = %@",object5);
NSLog(@"object6 = %@",object6);
NSLog(@"object7 = %@",object7);
NSLog(@"object8 = %@",object8);
NSLog(@"object9 = %@",object9);
}
@end
复制代码
打印:数组
2020-02-13 16:33:11.605468+0800 001---五大区Demo[1545:519291] ************堆区************
2020-02-13 16:33:11.605531+0800 001---五大区Demo[1545:519291] object1 = <NSObject: 0x600003854440>
2020-02-13 16:33:11.605601+0800 001---五大区Demo[1545:519291] object2 = <NSObject: 0x600003854460>
2020-02-13 16:33:11.605655+0800 001---五大区Demo[1545:519291] object3 = <NSObject: 0x600003854450>
2020-02-13 16:33:11.605720+0800 001---五大区Demo[1545:519291] object4 = <NSObject: 0x600003854470>
2020-02-13 16:33:11.605776+0800 001---五大区Demo[1545:519291] object5 = <NSObject: 0x600003854480>
2020-02-13 16:33:11.605840+0800 001---五大区Demo[1545:519291] object6 = <NSObject: 0x600003854490>
2020-02-13 16:33:11.605902+0800 001---五大区Demo[1545:519291] object7 = <NSObject: 0x6000038544a0>
2020-02-13 16:33:11.605965+0800 001---五大区Demo[1545:519291] object8 = <NSObject: 0x6000038544b0>
2020-02-13 16:33:11.606028+0800 001---五大区Demo[1545:519291] object9 = <NSObject: 0x6000038544c0>
复制代码
0x1开头
的数据通常为常量区
和静态区
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
int clA;
static int bssA;
static NSString *bssStr1;
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"************静态区************");
NSLog(@"clA == \t%p",&clA);
NSLog(@"bssA == \t%p",&bssA);
NSLog(@"bssStr1 == \t%p",&bssStr1);
}
@end
复制代码
打印:安全
2020-02-13 16:33:11.623869+0800 001---五大区Demo[1545:519291] ************静态区************
2020-02-13 16:33:11.623950+0800 001---五大区Demo[1545:519291] clA == 0x10ef2729c
2020-02-13 16:33:11.624016+0800 001---五大区Demo[1545:519291] bssA == 0x10ef272a0
2020-02-13 16:33:11.624067+0800 001---五大区Demo[1545:519291] bssStr1 == 0x10ef272a8
复制代码
0x1开头
的数据通常为常量区
和静态区
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
int clB = 10;
static int bssB = 10;
static NSString *bssStr2 = @"noah";
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"************常量区************");
NSLog(@"clB == \t%p",&clB);
NSLog(@"bssB == \t%p",&bssB);
NSLog(@"bssStr2 == \t%p",&bssStr2);
}
@end
复制代码
打印:bash
2020-02-13 16:33:11.624130+0800 001---五大区Demo[1545:519291] ************常量区************
2020-02-13 16:33:11.624192+0800 001---五大区Demo[1545:519291] clB == 0x10ef271c0
2020-02-13 16:33:11.624244+0800 001---五大区Demo[1545:519291] bssB == 0x10ef271d0
2020-02-13 16:33:11.624300+0800 001---五大区Demo[1545:519291] bssStr2 == 0x10ef271c8
复制代码
为何要使用taggedPointer?
假设要存储一个NSNumber对象,其值是一个整数。正常状况下,若是这个整数只是一个NSInteger的普通变量,在64位CPU下是占8个字节的。1个字节有8位,若是咱们存储一个很小的值,会出现不少位都是0的状况,这样就形成了内存浪费,苹果为了解决这个问题,引入了taggedPointer的概念。数据结构
TaggedPointer源码多线程
// 建立
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
if (tag <= OBJC_TAG_Last60BitPayload) {
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
} else {
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
// 编码
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
// 解码
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
复制代码
系统对
taggedPointer
进行了_objc_encodeTaggedPointer
编码,该编码的实现就是对value进行了objc_debug_taggedpointer_obfuscator
的异或操做,而在读取taggedPointer
的时候,经过_objc_decodeTaggedPointer
进行解码,仍是进行了objc_debug_taggedpointer_obfuscator
的异或操做,这样进行了两次异或操做就还原了初始值。架构
使用代码来验证taggedpointer类型并发
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int num1 = 10;
float num2 = 12;
double num3 = 14;
long num4 = 5;
NSNumber * number1 = @(num1);
NSNumber * number2 = @(num2);
NSNumber * number3 = @(num3);
NSNumber * number4 = @(num4);
NSLog(@"number1 = %@ - %p - 0x%lx",number1,&number1,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number1)));
NSLog(@"number2 = %@ - %p - 0x%lx",number2,&number2,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number2)));
NSLog(@"number3 = %@ - %p - 0x%lx",number3,&number3,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number3)));
NSLog(@"number4 = %@ - %p - 0x%lx",number4,&number4,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number4)));
}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
@end
复制代码
打印
2020-02-13 19:35:33.310386+0800 004-taggedPointer[3472:589831] number1 = 10 - 0x7ffee4bcc7e0 - 0xb0000000000000a2
2020-02-13 19:35:33.310471+0800 004-taggedPointer[3472:589831] number2 = 12 - 0x7ffee4bcc7d8 - 0xb0000000000000c4
2020-02-13 19:35:33.310529+0800 004-taggedPointer[3472:589831] number3 = 14 - 0x7ffee4bcc7d0 - 0xb0000000000000e5
2020-02-13 19:35:33.310578+0800 004-taggedPointer[3472:589831] number4 = 5 - 0x7ffee4bcc7c8 - 0xb000000000000053
复制代码
以number1为例,通过
_objc_decodeTaggedPointer
解码出来的值是0xb0000000000000a2
,能够看到倒数第二位是值,倒数第一位是类型,能够得出最后一位二、四、五、3
分别表明int long float double
类型
再来看看字符串类型
//
// ViewController.m
// 004-taggedPointer
//
// Created by cooci on 2019/4/8.
// Copyright © 2019 cooci. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSString * str1 = [NSString stringWithFormat:@"a"];
NSString * str2 = [NSString stringWithFormat:@"ab"];
NSString * str3 = [NSString stringWithFormat:@"abc"];
NSString * str4 = [NSString stringWithFormat:@"abcd"];
NSLog(@"str1 = %@ - %p - 0x%lx",str1,&str1,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str1)));
NSLog(@"str1 = %@ - %p - 0x%lx",str2,&str2,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str2)));
NSLog(@"str1 = %@ - %p - 0x%lx",str3,&str3,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str3)));
NSLog(@"str1 = %@ - %p - 0x%lx",str4,&str4,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str4)));
}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
@end
复制代码
打印:
2020-02-13 19:42:22.152170+0800 004-taggedPointer[3539:593334] str1 = a - 0x7ffee3bf27c0 - 0xa000000000000611
2020-02-13 19:42:22.152257+0800 004-taggedPointer[3539:593334] str1 = ab - 0x7ffee3bf27b8 - 0xa000000000062612
2020-02-13 19:42:22.152322+0800 004-taggedPointer[3539:593334] str1 = abc - 0x7ffee3bf27b0 - 0xa000000006362613
2020-02-13 19:42:22.152386+0800 004-taggedPointer[3539:593334] str1 = abcd - 0x7ffee3bf27a8 - 0xa000000646362614
复制代码
字符串类型解压出来的值,最后一位表明的是字符串长度,而
6一、6二、6三、64
对应的是ASCII的a、b、c、d
苹果将 isa 设计成了联合体,在 isa 中存储了与该对象相关的一些内存的信息,缘由也如上面所说,并不须要 64 个二进制位所有都用来存储指针。来看一下 isa 的结构:
// x86_64 架构
struct {
uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
uintptr_t shiftcls : 44; // 存放着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 是否须要使用 sidetable 来存储引用计数
uintptr_t extra_rc : 8; // 引用计数可以用 8 个二进制位存储时,直接存储在这里
};
// arm64 架构
struct {
uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
uintptr_t shiftcls : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 是否须要使用 sidetable 来存储引用计数
uintptr_t extra_rc : 19; // 引用计数可以用 19 个二进制位存储时,直接存储在这里
};
复制代码
注意这里的
has_sidetable_rc
和extra_rc
,has_sidetable_rc
代表该指针是否引用了 sidetable 散列表,之因此有这个选项,是由于少许的引用计数是不会直接存放在 SideTables 表中的,对象的引用计数会先存放在extra_rc
中,当其被存满时,才会存入相应的 SideTables 散列表中,SideTables 中有不少张 SideTable,每一个 SideTable 也都是一个散列表,而引用计数表就包含在 SideTable 之中。
散列表(Hash table,也叫哈希表),是根据建(Key)而直接访问在内存存储位置的数据结构。也就是说,它经过一个关于键值得函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
来看一下 NSObject.mm
中它们对应的源码:
// SideTables
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
// SideTable
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引用计数表
weak_table_t weak_table; // 弱引用表
// other code ...
};
复制代码
它们的关系以下:
// 获取一个sidetable
table = &SideTables()[obj];
// SideTables
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
复制代码
// StripedMap
template<typename T>
// 这里的模板规范了一个sidetable的样式
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
//若是是真机,StripeCount=8
enum { StripeCount = 8 };
#else
//模拟器就是StripeCount=64
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
// 散列算法
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
// 这个算法保证算出来的值是小于StripeCount的,这样就不会出现数据越界的状况
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
// 重写操做符
T& operator[] (const void *p) {
// 这里返回一个sidetable
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
...
};
复制代码
分离锁并非一种锁,而是一种对锁的用法。各个元素分别加一把锁就是咱们说的分离锁。
问:为何不用SideTables 直接包含自旋锁,引用计数表和弱引用表呢?
答:
这是由于在众多线程同时访问这个 SideTable 表的时候,为了保证数据安全,须要给其加上自旋锁,若是只有一张 SideTable 的表,那么全部数据访问都会出一个进一个,单线程进行,很是影响效率,虽然自旋锁已是效率很是高的锁,这会带来很是很差的用户体验。针对这种状况,将一张 SideTable 分为多张表的 SideTables,再各自加锁保证数据的安全,这样就增长了并发量,提升了数据访问的效率,这就是为何一个 SideTables 下涵盖众多 SideTable 表的缘由。
自旋锁:计算机科学用于多线程同步的一种锁,线程会反复检查锁变量是否可用。因为线程在这一过程当中保持执行(没有进入休眠),所以是一种忙等。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
自旋锁适用于小型数据
、耗时不多
的操做,速度很快
。
SideTable中包含一个
c++
Map RefcountMap refcnts
用来对象存储额外的引用计数,一个结构体weak_table_t weak_table
用来存储对象的弱引用数据RefcountMap refcnts 中经过一个size_t(64位系统中占用64位)来保存引用计数,其中1位用来存储固定标志位,在溢出的时候使用,一位表示正在释放中,一位表示是否有弱引用,其他位表示实际的引用计数 ![]()
全局弱引用表 weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
复制代码
弱引用表的内部结构
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
复制代码
DisguisedPtr<objc_object> referent
对象指针,还有一个容器类保存因此只需这个对象的弱引用4
的时候,使用数据结构来存储,当超过4
个的时候使用hash表进行存储,out_of_line_ness
默认为 ob00
,当弱引用数量大于4
的时候,设置为 REFERRERS_OUT_OF_LINE
ob10
,经过判断out_of_line_ness
来决定用什么方式存储weak_referrer_t *referrers
是一个二级指针实现的hash表