一、内存布局
1、内核区
用于系统级别的操作空间。
2、栈区
存放局部变量、方法参数、函数、方法指针
空间小
内存地址:一般为0x7
开头;
从高到低分配空间
栈区内存如何定位?
是通过sp寄存器
定位的。
3、堆区
存储对象、需要开辟空间(alloc、new、block copy等)
空间大
内存地址:一般为0x6
开头;
4、全局区
-
未初始化数据(.bss)
未初始化的全局变量、静态变量;
-
已初始化数据(.data)
初始化的全局变量、静态变量;
-
代码段(.txt)
程序代码,加载到内存中;
内存地址:一般为0x1
开头;
5、保留区
测试代码:内存五大区测试代码
二、内存管理方案
- Arc
- Mrc
- TaggedPointer:⼩对象-NSNumber,NSDate
- NONPOINTER_ISA:⾮指针型isa
- 散列表:引⽤计数表,弱引⽤表
1、TaggedPointer
1:Tagged Pointer专⻔⽤来存储⼩的对象,例如NSNumber和NSDate
2:Tagged Pointer指针的值不再是地址了,⽽是真正的是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。在堆中,也不需要malloc和free
3.在内存读取上有着3倍的效率,创建时⽐以前快106倍。
WWDC看taggedPointer
x86-64下的taggPointer
arm64下的taggedPointer
tagggedPointer 面试题
- (void)taggedPointerDemo {
self.queue = dispatch_queue_create("com.jim.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"jim"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"来了");
// 多线程 读和写
// setter -> retian release
for (int i = 0; i<100000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"我是一个非常长的字符串,请知悉"];
NSLog(@"%@",self.nameStr);
});
}
}
上面的代码运行会发生崩溃,因为上面的nameStr
是TaggedPointer
类型,下面的是NSCFString
类型,这是在多线程环境下操作,会进行频繁的读与写,写操作本质就是 setter
方法,而setter
内部会进行旧值的release
和新值的retain
,那么问题来了,TaggedPointer类型是小对象,在底层是不进行retain、 release
操作的,而上面的NSCFString
类型在异步条件下,可能会连续多次release,因此导致崩溃。
字符串的类型
2、Arc&Mrc
arc和mrc通过引用计数来管理内存,那么引用计数存在哪里? isa里面。
ISA的结构
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
nonpointer
:表示是否对 isa 指针开启指针优化
0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等
has_assoc
:关联对象标志位,0没有,1存在
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
shiftcls
: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。(相当于taggedPointer里面的payload)
magic
:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced
:指对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。
deallocating
:标志对象是否正在释放内存
has_sidetable_rc
:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位
extra_rc
:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc。
探索 retain
retain
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
return rootRetain(false, RRVariant::FastOrMsgSend);
}
rootRetain
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
//如果是TaggedPointer ,就不做retain处理
if (slowpath(isTaggedPointer())) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
oldisa = LoadExclusive(&isa.bits);
/* 不需要看
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return (id)this;
}
}
*/
//重点内容
do {
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);//将散列表里面的引用计数拿出来
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil;
} else {
return (id)this;
}
}
uintptr_t carry;//代表引用计数的数量
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
//extra_rc 引用计数加满了,溢出了
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
// extra_rc里面留一半,sidetable里面存一半
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (variant == RRVariant::Full) {
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//拷贝一半的引用计数到散列表里面
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
sidetable_retain
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
//这里加2,跟引用计数 存在sidetable哪个地方有关,存在倒数第二位,那么加2,就是倒数第二位加1
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
探索 release
跟retain 基本上一样,再次不做赘述。