栈区 0x7 建立临时变量时由编译器自动分配,在不须要的时候自动清除的变量的存储区。 里面的变量一般是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆同样,用户栈在程序执行期间能够动态地扩展和收缩。html
堆区 0x6 那些由 new alloc 建立的对象所分配的内存块,它们的释放系统不会主动去管,由咱们的开发者去告诉系统何时释放这块内存(一个对象引用计数为0是系统就会回销毁该内存区域对象)。通常一个 new 就要对应一个 release。在ARC下编译器会自动在合适位置为OC对象添加release操做。会在当前线程Runloop退出或休眠时销毁这些对象,MRC则需程序员手动释放。 堆能够动态地扩展和收缩。c++
静态区(未初始化数据).bss 程序运行过程内存的数据一直存在,程序结束后由系统释放程序员
常量区(已初始化数据).data 专门用于存放常量,程序结束后由系统释放面试
代码区 用于存放程序运行时的代码,代码会被编译成二进制存进内存的程序代码区数组
一般咱们建立对象,对象存储在堆中,对象的指针存储在栈中,若是咱们要找到这个对象,就须要先在栈中,找到指针地址,而后根据指针地址找到在堆中的对象。 这个过程比较繁琐,当存储的对象只是一个很小的东西,好比一个字符串,一个数字。去走这么一个繁琐的过程,无非是耗费性能的,因此苹果就搞出了TaggedPointer这么一个东西。bash
TaggedPointer是苹果为了解决32位CPU到64位CPU的转变带来的内存占用和效率问题,针对NSNumber、NSDate以及部分NSString的内存优化方案。架构
Tagged Pointer指针的值再也不是地址了,而是真正的值。因此,实际上它再也不是一个对象了,它只是一个披着对象皮的普通变量而已。因此,它的内存并不存储在堆中,也不须要malloc和free。async
Tagged Pointer指针中包含了当前对象的地址、类型、具体数值。所以Tagged Pointer指针在内存读取上有着3倍的效率,建立时比普通须要malloc跟free的类型快106倍。ide
这里有对TaggedPointer进行详细介绍函数
为何第二个for会崩溃?
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0 ; i<1000000; i++) {
self.str = @"abcd";
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i<1000000; i++) {
self.str = [NSString stringWithFormat:@"adfalkdjfldkasjflakjsdkflasf-- %d",I];
}
});
复制代码
答:taggedpointer。 在setproperty函数中,执行了objc_release(id obj)
。 因为大量的循环,致使了线程问题,使引用计数<=-1
。 可是因为第一个循环中的obj是taggedpointer类型的string,会直接return obj,并不会release。 可是这里release,retain的时候咋办呢,引用计数是一直往上增吗?并非,在objc_retain(id obj)
中,一样判断了obj->isTaggedPointer
,若是是true,就直接return obj。
要说isa,得先从对象开始。
NSObject -> Class -> objc_class -> objc_object -> isa_t
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
复制代码
isa_t是联合体,而后重点看ISA_BITFIELD,
nonpointer:表示是否对 isa 指针开启指针优化 , 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等。
has_assoc:关联对象标志位,0没有,1存在。
has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,若是有析构函数,则须要作析构逻辑, 若是没有,则能够更更快的释放对象。
shiftcls:存储类指针的值。开启指针优化的状况下,在 arm64 架构中有 33 位⽤来存储类指针的值。
magic:用于调试器判断当前对象是真的对象仍是没有初始化的空间 。
weakly_referenced:标志对象是否被指向或者曾经指向⼀一个 ARC 的弱变量,没有弱引用的对象能够更快释放。
deallocating:标志对象是否正在释放内存。
has_sidetable_rc:是否有用到散列表,当对象引⽤计数大于 10 时,则须要借⽤该变量存储进位。
extra_rc:当表示该对象的引用计数值,其实是引用计数值减 1。例如,若是对象的引用计数为 10,那么 extra_rc 为 9。 例:在__x86_64(mac)__的架构下,若是引用计数大于 255,引用计数将会发生溢出。 溢出时,则须要将has_sidetable_rc标记为1,将会将拿出**2的7次方(128,就是上面的RC_HALF)**放入散列表(sidetable)
那么has_sidetable_rc是怎么操做的呢?
SideTables是一个数组,里面存着不少SideTable。***(注意看有没有s)*** 这是对象引用计数溢出时,会调用这个方法,将一半的引用计数存入sideTable。
SideTable& table = SideTables()[this];
复制代码
那么这里要看的就应该是SideTables()
这里我以为能够理解成SideTables()
就是一个StripedMap
,继续看StripedMap
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是C++里的强制类型转换符。
//这里是将16进制转成10进制
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
// 这里StripeCount是64,看上面第755行
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
//重载中括号 , c++特有
//要让”[]”内的操做数支持const void类型
T& operator[] (const void *p) {
// 调用indexForPointer(),获取sidetable
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
...
}
复制代码
看完上面的代码+注释,咱们走一波lldb调试,分别打印各个参数
这里就是一个获取SideTable的过程
<1> SideTable& table = SideTables()[this];传入一个this指针对象 <2> 经过indexForPointer获取当前指针对象所对应的下标 <3> 经过array[indexForPointer(p)].value 返回一个SideTable
#####初探SideTable spinlock_t:自旋锁、 RefcountMap:引用计数Map,是个C++的Map
weak_table_t:全局弱引用表
#####SideTable操做 这里举个例子 sidetable_addExtraRC_nolock
在sideTable中添加RetainCount(RC)
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
// 经过SideTables() 获取SideTable
SideTable& table = SideTables()[this];
//获取引用计数的size
size_t& refcntStorage = table.refcnts[this];
// 赋值给oldRefcnt
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
// 若是oldRefcnt & SIDE_TABLE_RC_PINNED = 1
// 就是 oldRefcnt = 2147483648 (32位状况)
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
//引用计数也溢出判断参数
uintptr_t carry;
// 引用计数 add
//delta_rc左移两位,右边的两位分别是DEALLOCATING(销毁ing) 跟WEAKLY_REFERENCED(弱引用计数)
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
//若是sidetable也溢出了。
//这里我for了几百万次,也没有溢出,可见sidetable能容纳不少的引用计数
if (carry) {
// 若是是32位的状况 SIDE_TABLE_RC_PINNED = 1<< (32-1)
// int的最大值 SIDE_TABLE_RC_PINNED = 2147483648
// SIDE_TABLE_FLAG_MASK = 3
// refcntStorage = 2147483648 | (oldRefcnt & 3)
// 若是溢出,直接把refcntStorage 设置成最大值
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
复制代码
以上,to be continue~