内存布局

0xc0000000
换算一下就是3GB,这3GB交给系统程序的使用,还有1GB是给内核区使用。剩下的0x00400000
作为保留字段预留给系统,比如nil、底层发送信号之类的。
栈区:函数,⽅法
堆区:通过alloc分配的对象,block copy
BSS段:未初始化的全局变量,静态变量
数据段: 初始化的全局变量,静态变量
text:程序代码,加载到内存中
栈区内存地址:⼀般为:0x7开头
堆区内存地址:⼀般为:0x6开头
数据段,BSS内存地址:⼀般为:0x1开头
内存管理⽅案
TaggedPointer:⼩对象-NSNumber,NSDate
NONPOINTER_ISA:⾮指针型isa
散列表:引⽤计数表,弱引⽤表
TaggedPointer
- taggedpointer指针:既包含了指针,又包含了值。
- 对象:包含了值和指针地址。
- 小对象: 对int、float之类的值包装成对象,例:NSNumber,NSData之类很小的对象
我们先看一个程序
self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"abc"]; // alloc 堆 iOS优化 - taggedpointer
NSLog(@"%@",self.nameStr);
});
}
for (int i = 0; i<10000; i++) {
dispatch_async(self.queue, ^{
self.nameStr = [NSString stringWithFormat:@"壮志饥餐胡虏肉,笑谈渴饮匈奴血。"];
NSLog(@"%@",self.nameStr);
});
}
这个两个异步线程的循环,上面那个可以正常运行,而下面那个运行到一半就会发生奔溃。这是因为下面的程序多条线程,同时对一个对象释放。我们通过断点可以看到上面的string 是NSTaggedPointerString
类型,下面的string是__NSCFString
类型。当对象是TaggedPointer类型时不会进行retain和realease操作,所以不会发生崩溃。
在类的加载时,我们就会对_read_images
中TaggedPointer
进行处理
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;
}
}
现在的小对象地址都是进行了混淆处理之后的。
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;
}
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
NSNumber *number1 = @1;
NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
········
0xa000000000000621
__NSCFNumber-0xb2d6852fbc115960-1 - 0xb000000000000012
我们将一个小对象进行解码打印之后可以看到他的结构,不仅是他的地址,他的值也包含其中。
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
# define _OBJC_TAG_MASK (1UL<<63)
这里通过mask,来判断最高位是1的就是TaggedPointer
0xa -> 1 010
;010->2
0xb -> 1 011
;011->3
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
...
};
0xa就是NSString类型,0xa就是NSNumber 类型。
小对象不需要arc进行管理,常量区可以直接释放回收。
- Tagged Pointer专⻔⽤来存储⼩的对象,例如NSNumber和NSDate
- Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再
是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储
在堆中,也不需要malloc和free- 在内存读取上有着3倍的效率,创建时⽐以前快106倍。

NONPOINTER_ISA
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
-
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。如果引⽤计数⼤于 10,则需要使⽤到下⾯的 has_sidetable_rc。`
散列表(SiddeTables)
struct SideTable {
spinlock_t slock;//开解锁
RefcountMap refcnts;//引用技术表
weak_table_t weak_table;//弱引用表
};
散列表在内存中有8张。
如果只有一张,所有的对象都在一个表中,每次开锁,所有的都会暴露出来。若果每个对象都开一个表就会过度消耗性能。
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
delloc
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
如果没有满足条件直接free。
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);//清除cxx
if (assoc) _object_remove_assocations(obj);//移除关联对象
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();//散列表清除
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);//弱引用表清除
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}

retain
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
//直接操作散列表
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {//操作散列表
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
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.
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 (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();
return (id)this;
}
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
addc
方法之前说过,在这里就是找到extra_rc,然后进行+1操作。如果满了,就会走carry。
如果extra_rc溢出,就会将extra_rc的一半交给散列表。
retainCount
NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
···················
1
我们在分析alloc源码的时候并没有看到对extra_rc
的操作,打印的结果却是一。
查看retainCount源码
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
系统会在获取retainCount事检测是否为nonpointer,然后对其进行加一操作。
网友评论