内存五大区:
- 栈(Stack):
- 先进后出
- 系统自动管理
- 内存按顺序分配,通常存储局部变量、方法参数、对象指针。
- 存储空间有限:主线程
1M,子线程512k - 一般以
0x7开头
- 堆(Heap):
- 手动管理
- 内存随机分配
- 通常存储着通过
alloc,new,malloc创建的变量,C下需要调用free函数进行释放 - 一般以
0x6开头
- 静态(Static)/全局区(Global):
- 存储全局变量、静态变量
- 常量区(Constant):
- 存储着一些常量
- 一般以
0x1开头
- 代码区:
- 存储编译生成的二进制代码
数据类型
- 值类型
- 存储在栈上
- 无需对其进行内存管理
- 地址中存储的是值
- 引用类型
- 存储在堆上
- 需要对其进行内存管理
- 地址中存储的是堆区地址
iOS内存管理方案:
通常系统在对简单数据进行存储时,需要通过
栈区存储的指针地址,找到堆区空间,再从堆区读取到值,整个读取流程效率较低,占用空间大。所以,系统对其进行优化,将其标记为Tagged Pointer,其值存储在栈区,加快读取效率,减少占用空间
tagged point:是一个指针,指针中包含
tagged标记,针对小对象类型(长度小于9),在地址的最高位打上标记1,这个时候栈中存储的不再是地址,而是一个真正的值,而且在进程初始化时会与一个随机值进行混淆
NSString *firstString = @"helloworld";
NSString *secondString = [NSString stringWithFormat:@"helloworld"];
NSString *thirdString = @"hello";
NSString *fourthSting = [NSString stringWithFormat:@"hello"];
NSLog(@"%p %@",firstString,[firstString class]);
NSLog(@"%p %@",secondString,[secondString class]);
NSLog(@"%p %@",thirdString,[thirdString class]);
NSLog(@"%p %@",fourthSting,[fourthSting class]);
//输出
//0x101a32070 __NSCFConstantString 字符串常量,是一种 编译时常量,retainCount 值很大,对其操作不会引起引用计数变化,存储在`常量区`
//0x600003838e00 __NSCFString 运行时创建的 NSString子类,创建后引用计数默认为1,存储在`堆区`
//0x101a32090 __NSCFConstantString
//0xa08d84b039e90fdc NSTaggedPointerString 字符串优化类型,存储在`常量区`
tagged point 编码
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
//与 随机值(objc_debug_taggedpointer_obfuscator) 进行 异或 混淆
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
//是否是ARM64
#if OBJC_SPLIT_TAGGED_POINTERS
if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK)
return (void *)ptr;
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag);
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << _OBJC_TAG_INDEX_SHIFT;
#endif
return (void *)value;
}
随机数是在dyld读取image时_read_images方法中调用 initializeTaggedPointerObfuscator() 方法进行的生成
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
......
if (DisableTaggedPointers) {
disableTaggedPointers();
}
// 进程启动时初始化一个随机的值,用于混淆tagged pointer指针
initializeTaggedPointerObfuscator();
......
}
static void
initializeTaggedPointerObfuscator(void)
{
if (!DisableTaggedPointerObfuscation) {
// 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;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
objc_debug_taggedpointer_obfuscator = 0;
}
}
tagged point 解码
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
//ARM64 会进入 if
#if OBJC_SPLIT_TAGGED_POINTERS
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT);
value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT;
#endif
return value;
}
对上面 0xa08d84b039e90fdc 进行解码后,得到 0xa00006f6c6c65685,对其 p/t 输出之后得到地址里的内容
基于 inter处理器 地址内容分解
inter下头部接下来3位表示标签位,ARM64则是最底3位表示标签位,表示当前值的类型。
inter下最低4位来表示字符长度,ARM64标签位向前4位表示字符长度
//tagged 字符类型
// 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,
ARC:自动内存管理,编译器在编译时会自动为我们在合适的地方插入 retain,release,autorelease 方法,进行内存管理
引用计数
管理对象生命周期的一种方式,当其 ==0 时,该对象就会被释放掉,同时释放的还是有对象占用的内存空间
引用计数的存储
在一个对象
alloc的时候,会创建其isa指针,其创建isa的方法initIsa中,里边就存在记录对象引用计数的参数:extrc_rc
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) {
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
// 非nonpointer
newisa.setClass(cls, this);
} else {
...
// 创建isa,引用计数赋值为1
newisa.extra_rc = 1;
}
isa = newisa;
}
- 引用计数存储情况:
- 非
nonpointerIsa,直接存在sidetable中 - 是
nonpointerIsa,- 是否正在
被释放,是就返回 - 若不是,就存储在
extra_rc - 若
extra_rc存不下,就借位sidetable进行存储
- 是否正在
- 非
retain 原理
__attribute__((aligned(16), flatten, noinline))
objc_retain(id obj) {
// 如果是 tagged pointer 直接返回
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain(); }
ALWAYS_INLINE id
objc_object::rootRetain(**bool** tryRetain, objc_object::RRVariant variant)
{
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;
//不是nonpointerisa,引用计数存储在sideTable中
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;
}
}
//nonpointerisa下,直接操作 extra_rc
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//nonpointerisa下,extra_rc 存不下时
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
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.
// copy 出 extra_rc 的一半,放入到 sidetable 中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
//一半放入到 sidetable 中,打上标记
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 存入另一半 引用计数
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!transcribeToSideTable);
ASSERT(!sideTableLocked);
}
return (id)this;
}
方法流程:
1、判断是否是tagged pointer,是就return
2、获取对象isa,判断其是不是 nonpointisa,不是,引用计数就直接在side table中进行++
3、是 nonpointisa,看其是不是正在被释放,是就return
4、若不是,看extrc_rc能否存的下,能就直接存
5、若不能,extrc_rc 取出一半存储到 side table 中,并将 has_sidetable_rc 标记为 yes
release原理
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
// 如果是 tagged pointer,就return
if (obj->isTaggedPointerOrNil()) return;
return obj->release();
}
ALWAYS_INLINE bool
objc_object::rootRelease(**bool** performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
...
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
与retain大致相同,方法流程:
1、判断是否是tagged pointer,是就return
2、获取对象isa,判断其是不是 nonpointisa,不是,引用计数就直接在side table中进行--
3、是 nonpointisa,看其是不是正在被释放,是就return
4、若不是,就对 extrc_rc 进行 -- 操作
5、当 extrc_rc == 0 时,判断 has_sidetable_rc 是否为yes
6、是, 取出 side table 中的值 给 extrc_rc,进行 -- 操作,has_sidetable_rc 标记为 no
7、当 引用计数为 0 时,调用 dealloc
dealloc原理
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && // 是nonpointerisa
!isa.weakly_referenced && // 无弱引用
!isa.has_assoc && // 无关联地对象
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor && // 无析构函数
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc)) // 无借位
{
assert(!sidetable_present());
//释放
free(this);
}
else {
// 调用C++析构函数、删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据
object_dispose((id)this);
}
}
释放流程:当 isa 是 nonpointerisa,并且 无弱引用、无关联对象、无析构函数、无借位是进行释放。
否则调用 object_dispose() 进行删除关联对象、清空散列表中引用计数信息、清空弱引用表中相关数据 操作
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。