一、内存布局
| 内核区:(例:4bg内存只有3gb用来储存信息,1bg给内核区↓地址为0xc0000000←3bg↓)高 |
|---|
| 栈区 ---------------------------- 方法、int等临时变量(栈帧逐渐从上往下往堆的方向扩展) 堆区的增长则是自下而上,若碰面即为堆栈(内存)溢出 ---------------------------- 堆区 |
| 未初始化数据(.bss)段:全局静态变量--也称为静态区 |
| 已初始化数据(.data)段--也称为常量区 |
| 代码段(.text):二进制程序存放 |
| 保留段:给系统提供必要的保留空间(↑0x00400000↑)低 |
- 程序加载到内存会走向未初始化数据(.bss)段、已初始化数据(.data)段和代码段(.text)
// 0x1 常量 静态
int a = 10;
NSLog(@"%p",&a); // 栈 -- 0x7 一般情况都是 栈
NSObject *obj = [NSObject new]; // 对象 --
NSLog(@"%@ - %p",obj,&obj); // 0x6 堆 - 指针的变量存在栈区
NSArray *array = [[NSArray alloc] init];
NSLog(@"%@-%p",array,&array);
// 若想找到一个对象,先在栈区找到其指针,在根据指针所指的堆区中的位置读取其值
// 尽量采用封装方法 :随着方法中的变量增多,会更加难以读取,所以要增加嵌套,以空间换时间
// 疑问:重写description、方法和函数的区别
二、内存管理方案
1、TaggedPointer:小对象-NSNumber、NSDate
-
NSNumber 有 2^31 位
1字节 = 8位
int : 4字节*8 = 32
导致一般用NSNumber会大量浪费内存(因为一般存入的数字远小于NSNumber的大小)
-
~ 按位取反 : 100001 ~ 011110
-
10000111 <<3 左移3位 10000111000
-
10000111 >>3 右移3位 10000
-
^ 异或操作:两个相同为0,不同为1 , 常用于翻转和交换
-
交换:
a = 1010 0001
b = 0000 1100
a = a^b // 1010 1101
b = a^b // 1010 0001
a = a^b // 0000 1100
-
源码:
static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) { // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. // They are reversed here for payload insertion. // assert(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); 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 { // assert(tag >= OBJC_TAG_First52BitPayload); // assert(tag <= OBJC_TAG_Last52BitPayload); // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); 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); // 做标记,表明为taggedpointer,不是真正的对象,而是个值 // 告诉系统这是个小对象,不需要太麻烦的处理 } static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; }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; } }-
手动将系统编码的NSNumber编码的对象解码后会发现,解码后对象地址最后一位为类型
int :2
float :4
long :3
double:5
-
紧挨末尾的倒数位为NSNumber的值
-
说明小对象类型的地址不止存储地址空间,还会存储其类型和值
也就不需要去栈里找指针,再去堆里找值,也不用64位存储
(牛逼哇)
-
NSString里中文为CFString,因为中文是特殊符号,如果是abcd这种的就会是TaggedPointerString类型,但是长度太长的话也会变成CFString,因为已经不是小对象了
-
TaggedPointerString在每次赋值(setter)的时候不需要retain和release,但是CFString则需要一直retain和release,如果处于多线程中,就有可能崩溃
-
在内存读取上有3倍的效率,创建时比以前快106倍
-
2、NONPOINTER_ISA:非指针类型isa
7 |0|0|0|0|0|0|0|0| 0
-
联合体union
union { char bits; // 0000 0000 // 位域(顺延) struct { char front : 1; char back : 1; char left : 1; char right : 1; }; } _direction;从右往左:
-
第一位 nonpointer :
表示是否对isa指针开启指针优化
0:纯isa指针 1:不止是类对象地址,isa中包含了类信息、对象的引用计数等
-
第二位 has_assoc :
关联对象标志位
0没有,1存在
-
第三位 has_cxx_dtor :
该对象是否有C++或者Objec的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象
-
shiftcls :
存储类指针的值。开启指针优化的情况下,在arm64位中有33位用来存储类指针
疑问:什么是类指针,为什么要这么多
- magic :
用于调试器判断当前对象是真的对象还是没有初始化的空间
- weakly_referenced :
标志对象是否被只想或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放
- deallocating :
标志对象是否正在释放内存
- has_sidetable_rc :
当对象引用计数大于10时,则需要借用该变量存储进位
-
接下来19位 extra_rc :
当表示该对象的引用计数值,实际上是引用计数之减1
例如,如果对象的引用计数为10,那么extra_rc 为 9 。如果引用计数大于10,则需要使用到下面的has_sidetable_rc
3、散列表:引用计数,弱引用表
table = &SideTables()[obj];
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
-
SideTables() 中包含了很多张SideTable
-
SideTable中含有
- Spinlock_t :自旋锁
- RefcountMap:引用计数表
- weak_table_t : 弱引用表
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } void lock() { slock.lock(); } void unlock() { slock.unlock(); } void forceReset() { slock.forceReset(); } // Address-ordered lock discipline for a pair of side tables. template<HaveOld, HaveNew> static void lockTwo(SideTable *lock1, SideTable *lock2); template<HaveOld, HaveNew> static void unlockTwo(SideTable *lock1, SideTable *lock2); }; -
为什么是多张sideTable,而不是一张表?
在同一张表中,若修改一个属性则另一个属性需要等待,因为表的自旋锁在忙等
-
StripedMap
static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); }template<typename T> class StripedMap { #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; #endif struct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; static unsigned int indexForPointer(const void *p) { // reinterpret_cast 十六进制转十进制 uintptr_t addr = reinterpret_cast<uintptr_t>(p); return ((addr >> 4) ^ (addr >> 9)) % StripeCount; // 防止散列冲突/数组越界的方法 } public: // 重写了操作符 (啥是重装操作符) T& operator[] (const void *p) { // 获取散列表 return array[indexForPointer(p)].value; // 外部传入的对象是p } const T& operator[] (const void *p) const { return const_cast<StripedMap<T>>(this)[p]; } // Shortcuts for StripedMaps of locks. void lockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.lock(); } } void unlockAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.unlock(); } } void forceResetAll() { for (unsigned int i = 0; i < StripeCount; i++) { array[i].value.forceReset(); } } void defineLockOrder() { for (unsigned int i = 1; i < StripeCount; i++) { lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value); } } void precedeLock(const void *newlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock); } void succeedLock(const void *oldlock) { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(oldlock, &array[0].value); } const void *getLock(int i) { if (i < StripeCount) return &array[i].value; else return nil; } #if DEBUG StripedMap() { // Verify alignment expectations. uintptr_t base = (uintptr_t)&array[0].value; uintptr_t delta = (uintptr_t)&array[1].value - base; assert(delta % CacheLineSize == 0); assert(base % CacheLineSize == 0); } #else constexpr StripedMap() {} #endif }; -
RefcountMap
// RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`. typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap; // 就是个存key和value的map,不是个table