一、内存五大区
iOS中,常说的堆区、栈区都是指的虚拟内存,主要分以下五大区
-
堆区:由程序员分配和释放,用于存放运行中被动态分配的内存段,大小不定,可以增加或缩减,由低到高,在iOS的ARC程序中,系统自动管理计数器,计数器为0的时候,当次runloop结束后,释放掉内存。堆中的地址通过指针访问,要访问堆中内存时,一般先需要通过栈区的指针地址,然后通过指针地址访问堆区,OC中使用
alloc或者new开辟空间创建对象,C语言中使用mallo、calloc、realloc分配空间,free释放,一般0x6开头 -
栈区:栈由编译器分配和释放,用于存方程序临时的变量,参数,局部变量等,是一块连续的内存区域,先进后出,由高向低,iOS主线栈大小是
1MB,其它线程是512KB,一般0x7开头 -
全局区(静态区):全局区分为未初始化区:bss段和初始化全局区:data段,一般0x1开头
-
常量区:存放
常量,已初始化的全局变量,已初始化的静态变量,空间由系统管理,生命周期为整个程序运行期,一般0x1开头 -
代码区:存放函数的二进制代码
二、iOS内存管理的方案
2.1、nonopointerISA
nonopointerISA: 0:纯isa指针,1:不止是类对象地址,isa包含类信息、对象的引用计数等
查看alloc源码调试定位到initIsa
allocWithZone -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
isa_t newisa(0);
*****
newisa.extra_rc = 1;
}
isa = newisa;
}
注意到extra_rc,这里extra_rc就是对象的引用计数,说明苹果的优化是把引用计数存储到对象的isa中
护展-isa_t详解
查看ISA结构体位域
# arm64 //对应iOS移动端
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD
uintptr_t nonpointer : 1; //表示是否开启指针优化,0:纯isa指针 1:不止是类对象地址,isa包含了类信息、对象的引用计数等
uintptr_t has_assoc : 1; // 关联对象标志位,0没有,1存在
uintptr_t has_cxx_dtor : 1; //该对象是否有C++或者Objc的析构器,如果有析构函数,则需有析构逻辑,如果没有,则可以更快的释放对旬
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ //存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
uintptr_t magic : 6; //用于调试器判断当前对象是真的对象还没有初始化空间
uintptr_t weakly_referenced : 1; //标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
uintptr_t unused : 1;//标志对象是否正在释放内存
uintptr_t has_sidetable_rc : 1;//当对象引用技术大于 10 时,则需要借用该变量存储进位
uintptr_t extra_rc : 19 //当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__ //对应MacOS端
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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*/ //存储类指针的值。开启指针优化的情况下,在 x86 架构中有 44 位用来存储类指针。
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
2.2、tagged point
5S之后的一种优化,小对象类型 NSNumber NSData NSString,打上tagged标签后,不再是地址,而是真正的值
NSString *firstString = @"helloworld";
NSString *secondString = [NSString stringWithFormat:@"helloworld"];
NSString *thirdString = @"hello";
NSString *fourthString = [NSString stringWithFormat:@"hello"];
NSLog(@"%p %@",firstString,[firstString class]);
NSLog(@"%p %@",secondString,[secondString class]);
NSLog(@"%p %@",thirdString,[thirdString class]);
NSLog(@"%p %@",fourthString,[fourthString class]);
打印输出
2022-07-27 16:12:41.065277+0800 OCTest[53504:3088675] 0x100a7c070 __NSCFConstantString
2022-07-27 16:12:41.065357+0800 OCTest[53504:3088675] 0x282c556a0 __NSCFString
2022-07-27 16:12:41.065386+0800 OCTest[53504:3088675] 0x100a7c090 __NSCFConstantString
2022-07-27 16:12:41.065410+0800 OCTest[53504:3088675] 0xb3a7ee3798a9d5ec NSTaggedPointerString
NSCFConstantString:常量区
NSCFString:长字符串通过StringWithFormat创建时会出现,引用计数为1
NSTaggedPointerString:字符串长度小于9时自动生成
tagged point
tagged point:在总是为0的位置打上了标记的指针 -- 真正的值,与随机值混淆
inter最低位为1,arm最高位为1,即表示是tagged point,iOS13以后,最低的三位表示标签
源码分析
源码中搜索_objc_encodeTaggedPointer
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
#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;
}
可以看到,用objc_debug_taggedpointer_obfuscator和ptr异或运算混淆,搜索objc_debug_taggedpointer_obfuscator
/***********************************************************************
* initializeTaggedPointerObfuscator
* Initialize objc_debug_taggedpointer_obfuscator with randomness.
*
* The tagged pointer obfuscator is intended to make it more difficult
* for an attacker to construct a particular object as a tagged pointer,
* in the presence of a buffer overflow or other write control over some
* memory. The obfuscator is XORed with the tagged pointers when setting
* or retrieving payload values. They are filled with randomness on first
* use.
**********************************************************************/
static void
initializeTaggedPointerObfuscator(void)
其中initializeTaggedPointerObfuscator是在类的加载时调用,初始化一个随机值,用来混淆tagged point指针
那如何查看混淆前的值呢,查看源吗中有_objc_decodeTaggedPointer代码
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr);
#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;
}
所以我们可以得到编解码的代码
#define lg_OBJC_TAG_INDEX_MASK 0x7UL
#define lg_OBJC_TAG_INDEX_SHIFT 0
extern uintptr_t objc_debug_taggedpointer_obfuscator;
extern uint8_t objc_debug_tag60_permutations[8];
uintptr_t lg_objc_obfuscatedTagToBasicTag(uintptr_t tag) {
for (unsigned i = 0; i < 7; i++)
if (objc_debug_tag60_permutations[i] == tag)
return i;
return 7;
}
//混淆解码
uintptr_t
lg_objc_decodeTaggedPointer(id ptr)
{
uintptr_t value = (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
uintptr_t basicTag = (value >> lg_OBJC_TAG_INDEX_SHIFT) & lg_OBJC_TAG_INDEX_MASK;
value &= ~(lg_OBJC_TAG_INDEX_MASK << lg_OBJC_TAG_INDEX_SHIFT);
value |= lg_objc_obfuscatedTagToBasicTag(basicTag) << lg_OBJC_TAG_INDEX_SHIFT;
return value;
}
static inline uintptr_t lg_objc_basicTagToObfuscatedTag(uintptr_t tag) {
return objc_debug_tag60_permutations[tag];
}
//混淆编码
void *
lg_objc_encodeTaggedPointer(uintptr_t ptr)
{
uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr);
uintptr_t basicTag = (value >> lg_OBJC_TAG_INDEX_SHIFT) & lg_OBJC_TAG_INDEX_MASK;
uintptr_t permutedTag = lg_objc_basicTagToObfuscatedTag(basicTag);
value &= ~(lg_OBJC_TAG_INDEX_MASK << lg_OBJC_TAG_INDEX_SHIFT);
value |= permutedTag << lg_OBJC_TAG_INDEX_SHIFT;
return (void *)value;
}
还原并打印混淆前的地址
NSLog(@"0x%lx",lg_objc_decodeTaggedPointer(fourthString));
//打印输出
0x800037b63632b42a
查看0x800037b63632b42a二进制
(lldb) p/t 0x800037b63632b42a
(unsigned long) $0 = 0b1000000000000000001101111011011000110110001100101011010000101010
其中最高位为1表示是tagged Point,低三位010表示类型
源码搜索objc_tag
{
// 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,
****
};
由此可得010为2,表示是NSString类型
1 - 逐位分析
0000000000000000 - 空白
01101111 - 111-对应ascii码是 o
01101100 - 108-对应ascii码是 l
01101100 - 108-对应ascii码是 l
01100101 - 101-对应ascii码是 e
01101000 - 104-对应ascii码是 h
0101 - 表示长度,hello长度为5
010 - 最低位表示是NSString
由些可见,tagged point是直接存储值
源码查看objc_retain分析
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
//如果是tagedPoint类型直接返回,不走引用计数
if (_objc_isTaggedPointerOrNil(obj)) return obj;
return obj->retain();
}
objc_object::retain()
{
ASSERT(!isTaggedPointer());//断言再次判断是是否是taggedPoint
return rootRetain(false, RRVariant::FastOrMsgSend);
}
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);
......
}
2.3、sidetable
sidetable数据结构
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);
};
这个结构中包含一个spinlock_t互斥锁,一个RefcountMap的引用计数表,一个weak_table_t弱引用表
三、源码分析
3.1、objc_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) {
**********
}
if (slowpath(!oldisa.nonpointer)) {
**********
}
//存储引用计数由下面的do-while循环实现
do {
transcribeToSideTable = false;
newisa = oldisa;
//1、不是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;
}
}
//2、是nonopointerias extrc_rc能够存下,extra_rc++
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//3、是nonpointerisa extra_rc存不下 -- sidetable
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
//copy一半的计用计数存到side table
// 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 (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;
}
3.2、objc_release
objc_release(id obj)
{
if (_objc_isTaggedPointerOrNil(obj)) return;
return obj->release();
}
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());
rootRelease(true, RRVariant::FastOrMsgSend);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
if (slowpath(isTaggedPointer())) return false;
**********
//release的核心方法是下面的do-while循环
retry:
do {
newisa = oldisa;
//1、不是nonpointerisa,直接操作sideTable
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
//2、判断是否在被释放,如果是,直接返回
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
//3、extra_rc--,当extra_rc减到0时,跳到underflow
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else {
ASSERT(!sideTableLocked);
}
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//判断isa中has_sidetable_rc标记位是否为true,如果是,跳转到rootRelease_underflow
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
****************
}
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa.isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
//引用计数减到0,发一个dealloc消息
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
3.3、dealloc
- (void)dealloc {
_objc_rootDealloc(self);
}
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?,是否是taggedPointer
if (fastpath(isa.nonpointer && //是否是nonpointer
!isa.weakly_referenced && //是否有弱引用
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor && //是否有cxx的析构函数
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc)) //是否has_sidetable_rc为true
{
assert(!sidetable_present());
free(this);//直接free
}
else {
//有上面的情况执行object_dispose
object_dispose((id)this);
}
}
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); //
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
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();
}
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
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();
}
四、总结
4.1、 retain总结
1、判断是否是taggedpoint,是就直接返回
2、获取对象的isa
3、判断是否为nonpintISA
4、不是nonpintISA,直接将引用计数存到sidetable
5、是nonpintISA,对象是否正在被释放,直接返回
6、extra_rc能够存下,直接extra_rc++
7、extra_rc存不下,has_sidetable置为yes,将一半的引用计数存入sidetable,extra_rc赋值减半
4.2、 release总结
1、判断是否是taggedpoint,是就直接返回
2、获取对象的isa
3、判断是否为nonpintISA
4、不是nonpintISA,直接操作sidetable,sidetable--,如果为0 dealloc
5、是nonpintISA,对extra_rc--
6、isa extra_rc为0时,判断has_sidetable是否有值
7、has_sidetable有值,获取sidetable引用计数并减1,引用计数赋什给isa,has_sidetable - false
8、如果引用计数为0 -- dealloc
4.3、dealloc总结
1、判断是否是taggedpoint,是就直接返回
2、如果是taggedpoint,而且没有弱引用、没有关联对象、没有c++析构函数,没有散列表引用计数,则直接释放
3、如果是taggedpoint,不是第二步的情况,调用objc_destructInstance
4、删除关联对象,调用c++析构函数,清空弱引用表里面的数据,清空散列表里面的引用计数的信息