內存佈局中有七個區,分別是
內核區
、堆區
、棧區
、未初始化數據(靜態區)
、已初始化數據(常量區)
、代碼段
、保留區
。 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的源碼:// 獲取一個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中包含一個
RefcountMap refcnts 中經過一個size_t(64位系統中佔用64位)來保存引用計數,其中1位用來存儲固定標誌位,在溢出的時候使用,一位表示正在釋放中,一位表示是否有弱引用,其他位表示實際的引用計數c++
Map RefcountMap refcnts
用來對象存儲額外的引用計數,一個結構體weak_table_t weak_table
用來存儲對象的弱引用數據
全局弱引用表 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表