广州市建设工程定额管理网站,wordpress 敏感词过滤,汕头市建设工程信息网,网站索引量是什么意思内存分区 内存一般分为五大区#xff1a;栈区、堆区、常量区、全局区、代码区。如图 1.栈区
是由编译器自动分配并释放的#xff0c;主要用来存储局部变量、函数的参数等#xff0c;是一块连续的内存区域#xff0c;遵循先进后出#xff08;FILO#xff09;原则。一般在…内存分区 内存一般分为五大区栈区、堆区、常量区、全局区、代码区。如图 1.栈区
是由编译器自动分配并释放的主要用来存储局部变量、函数的参数等是一块连续的内存区域遵循先进后出FILO原则。一般在运行时分配。它的分配由高地址空间向低地址空间分配。
优点因为栈是由编译器自动分配并释放的不会产生内存碎片所以快速高效。 缺点栈的内存大小有限制数据不灵活。
例如下图创建两个变量存放在栈区地址是递减4。 2.堆区
堆区是由程序员手动管理。 主要用来存放动态分配的对象类型数据。是不连续的内存区域。在MRC环境下由程序员手动释放在ARC环境下由编译器自动释放。一般在运行时分配。它的分配是从低地址空间向高地址空间分配。 优点灵活方便数据适应面广泛。 缺点需手动管理速度慢、容易产生内存碎片。
例如下图创建两个对象存放在堆区地址递增。 3.常量区
常量区存放的就是字符串常量和基本类型常量。在编译时分配程序结束后回收
4.全局区/静态区
全局变量和静态变量的存储是放在一起的初始化的全局变量和静态变量存放在.data段未初始化的全局变量和静态变量在相邻的.bss区域在编译时分配程序结束后由系统释放。
5.代码区
代码区是在编译时分配主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的 内存管理 内存管理方案
1.TaggedPointer
2013 年 9 月苹果推出了首个采用 64 位架构的 A7 双核处理器的手机 iPhone5s为了改进从 32 位 CPU 迁移到 64 位 的内存浪费和效率问题在 64 位 环境下苹果工程师提出了 Tagged Pointer 的概念。采用这一机制系统会对 NSString、NSNumber 和 NSDate 等对象进行优化。建议大家看看 WWDC2020 这个视频的介绍。
Tagged Pointer 专门用来存储小对象例如NSNumberNSDate等Tagged Pointer指针的值不再是单纯的地址了而是真正的值所以实际上它也不再是一个对象了它只是一个披着对象皮的普通变量而已所以它的内存并不存储在堆区也不需要malloc和free。这样在读取上有着3倍的效率创建时候比以前快106倍。 由上图可以看出NSdate是NSTaggedPointer此外当字符串的长度为10个以内时字符串的类型都是NSTaggedPointerString类型当超过10个时字符串的类型才是__NSCFString NSTaggedPointer标志位
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{return ((uintptr_t)ptr _OBJC_TAG_MASK) _OBJC_TAG_MASK;
}
上面这个方法我们看到判断一个对象类型是否为NSTaggedPointerString类型实际上是讲对象的地址与_OBJC_TAG_MASK进行按位与操作,结果在跟_OBJC_TAG_MASK进行对比,我们在看下_OBJC_TAG_MASK的定义:
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
我们都知道一个对象地址为64位二进制它表明如果64位数据中最高位是1的话则表明当前是一个tagged pointer类型。
那么我们在看下上面打印出的地址所有NSTaggedPointerString地址都是0xd开头d转换为二进制1110根据上面的结论我们看到首位为1表示为NSTaggedPointerString类型。在这里得到验证。
注意:TaggedPointer类型在iOS和MacOS中标志位是不同的iOS为最高位而MacOS为最低位
对象类型
正常情况下一个对象的类型是通过这个对象的ISA指针来判断的那么对于NSTaggedPointer类型我们如何通过地址判断对应数据是什么类型的呢
在objc4-723之前我们可以通过与判断TaggedPointer标志位一样根据地址来判断而类型的标志位就是对象地址的61-63位,比如对象地址为0xa开头那么转换成二进制位1010,那么去掉最高位标志位后剩余为010,即10进制中的2。
接着我们看下runtime源码objc-internal.h中有关于标志位的定义如下
#if __has_feature(objc_fixed_enum) || __cplusplus 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{// 60-bit payloadsOBJC_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 reservedOBJC_TAG_RESERVED_7 7, // 52-bit payloadsOBJC_TAG_Photos_1 8,OBJC_TAG_Photos_2 9,OBJC_TAG_Photos_3 10,OBJC_TAG_Photos_4 11,OBJC_TAG_XPC_1 12,OBJC_TAG_XPC_2 13,OBJC_TAG_XPC_3 14,OBJC_TAG_XPC_4 15,OBJC_TAG_First60BitPayload 0, OBJC_TAG_Last60BitPayload 6, OBJC_TAG_First52BitPayload 8, OBJC_TAG_Last52BitPayload 263, OBJC_TAG_RESERVED_264 264
};
#if __has_feature(objc_fixed_enum) !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
objc4-750之后
// Returns a pointer to the classs storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{uintptr_t tagObfuscator ((objc_debug_taggedpointer_obfuscator _OBJC_TAG_INDEX_SHIFT) _OBJC_TAG_INDEX_MASK);uintptr_t obfuscatedTag tag ^ tagObfuscator;// Array index in objc_tag_classes includes the tagged bit itself
#if SUPPORT_MSB_TAGGED_POINTERS 高位优先return objc_tag_classes[0x8 | obfuscatedTag];
#elsereturn objc_tag_classes[(obfuscatedTag 1) | 1];
#endif
} classSlotForBasicTagIndex() 函数的主要功能就是根据指定索引 tag 从数组objc_tag_classes中获取类指针,而下标的计算方法发是根据外部传递的索引tag。比如字符串 tag 2。当然这并不是简单的从数组中获取某条数据。
获取TaggedPointer的值
objc4-750之后源码
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{// assert(_objc_isTaggedPointer(ptr));uintptr_t value _objc_decodeTaggedPointer(ptr);uintptr_t basicTag (value _OBJC_TAG_INDEX_SHIFT) _OBJC_TAG_INDEX_MASK;if (basicTag _OBJC_TAG_INDEX_MASK) {return (value _OBJC_TAG_EXT_PAYLOAD_LSHIFT) _OBJC_TAG_EXT_PAYLOAD_RSHIFT;} else {return (value _OBJC_TAG_PAYLOAD_LSHIFT) _OBJC_TAG_PAYLOAD_RSHIFT;}
}static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr)
{// assert(_objc_isTaggedPointer(ptr));uintptr_t value _objc_decodeTaggedPointer(ptr);uintptr_t basicTag (value _OBJC_TAG_INDEX_SHIFT) _OBJC_TAG_INDEX_MASK;if (basicTag _OBJC_TAG_INDEX_MASK) {return ((intptr_t)value _OBJC_TAG_EXT_PAYLOAD_LSHIFT) _OBJC_TAG_EXT_PAYLOAD_RSHIFT;} else {return ((intptr_t)value _OBJC_TAG_PAYLOAD_LSHIFT) _OBJC_TAG_PAYLOAD_RSHIFT;}
}
实例代码和结果 NSString *str1 [NSString stringWithFormat:1];NSString *str11 [NSString stringWithFormat:11];NSString *str2 [NSString stringWithFormat:2];NSString *str22 [NSString stringWithFormat:22];// 0x31 1 0x32 1uintptr_t value1 objc_getTaggedPointerValue((__bridge void *)str1);uintptr_t value2 objc_getTaggedPointerValue((__bridge void *)str2);uintptr_t value11 objc_getTaggedPointerValue((__bridge void *)str11);uintptr_t value22 objc_getTaggedPointerValue((__bridge void *)str22);// 以16进制形式输出NSLog(%lx, value1);NSLog(%lx, value11);NSLog(%lx, value2);NSLog(%lx, value22); TaggedPointer[89535:3033433] 311
TaggedPointer[89535:3033433] 31312
TaggedPointer[89535:3033433] 321
TaggedPointer[89535:3033433] 32322 TaggedPoint对象是一个特殊的对象不会涉及到引用计数retain、release等内存操作。对象的值就存在指针中不过值通过了一份加密。
2.NONPOINTER_ISA
上面我们说了对于一个对象的存储苹果做了优化那么对于ISA指针呢
对象的isa指针用来表明对象所属的类类型。
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指针最后实际上落脚于isa_t的联合类型。那么何为联合类型呢 联合类型是C语言中的一种类型是一种n选1的关系,联合的作用在于用更少的空间表示了更多的可能的类型虽然这些类型是不能够共存的。比如isa_t 中包含有clsbits struct三个变量它们的内存空间是重叠的。在实际使用时仅能够使用它们中的一种你把它当做cls就不能当bits访问你把它当bits就不能用cls来访问。
对于isa_t联合类型主要包含了两个构造函数isa_t(),isa_t(uintptr_t value)和三个变量cls,bits,struct,而uintptr_t的定义为typedef unsigned long。
当isa_t作为Class cls使用时这符合了我们之前一贯的认知isa是一个指向对象所属Class类型的指针。然而仅让一个64位的指针表示一个类型显然不划算。
因此绝大多数情况下苹果采用了优化的isa策略即isa_t类型并不等同而Class cls, 而是struct。
下面我们先来看下struct的结构体
// ISA_BITFIELD定义如下
# if __arm64__
# 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 (1ULL45)
# define RC_HALF (1ULL18) 注意:成员后面的表明了该成员占用几个bit 而每个成员的意义如下表 nonopointer表示是否对isa指针开启了指针优化
0纯isa指针1不止是类对象地址isa中包含了类信息对象的引用计数等。
has_assoc:关联对象标志位0没有1存在
has_cxx_dtor该对象是否有c或者Objc的析构器如果有析构器函数则需要做析构逻辑如果没有则可以更快的释放。 shiftcls存储指针的值开启指针优化的情况下再arm64架构中有33位用来存储类指针 magic用于调试器判断当前对象是真的对象还是未初始化的空间。
weakly_referenced:标致对象是否指向或者曾经指向一个ARC的若变量没有弱引用的对象可以更快的释放。
deallcating标志对象是否正在释放内存
has_sidetable_rc当前对象引用计数大于10的时则需要借用变量存储进位 extra_rc当表示该对象的引用计数值实际上是引用计数值减1例如如果对象的引用计数为10那么extra_rc为9如果用于计数大于10则需要使用下面的has_sidetable_rc。 3.散列表、引用计数表
Sidetable主要包含spinlock引用计数(存放extra_rc接收的另一半引用计数)弱引用表。
truct SideTable {spinlock_t slock;// 存放从extra_rc接收的那一半引用计数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.templateHaveOld, HaveNewstatic void lockTwo(SideTable *lock1, SideTable *lock2);templateHaveOld, HaveNewstatic void unlockTwo(SideTable *lock1, SideTable *lock2);
};
1.spinlock_t 加锁
spinlock_t 不是自旋锁在底层代码查找的过程中我们可以发现他是一把os_unfair_lock锁在使用sidetable的时候频繁的读取需要加锁一张表无疑影响了效率因此我们采用stripedMap来分散压力且stripedMap的数量是根据系统来确定的真机模式下sidetable最多为8张虚拟机等为64张).
// 上面 SideTables 的实现
static StripedMapSideTable SideTables() {return SideTablesMap.get();
}
2.RefcountMap(引用计数表)
RefcountMap本身从DenseMap得来 typedef objc::DenseMapDisguisedPtrobjc_object,size_t,RefcountMapValuePurgeable RefcountMap;存放从extra_rc接收的那一半引用计数
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);
}
接着来看一下 sidetable_addExtraRC_nolock 方法
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{ASSERT(isa.nonpointer);// 获取SideTables,也就是StripeMapSideTable table SideTables()[this];size_t refcntStorage table.refcnts[this];size_t oldRefcnt refcntStorage;// isa-side bits should not be set hereASSERT((oldRefcnt SIDE_TABLE_DEALLOCATING) 0);ASSERT((oldRefcnt SIDE_TABLE_WEAKLY_REFERENCED) 0);if (oldRefcnt SIDE_TABLE_RC_PINNED) return true;uintptr_t carry;size_t newRefcnt addc(oldRefcnt, delta_rc SIDE_TABLE_RC_SHIFT, 0, carry);if (carry) {refcntStorage SIDE_TABLE_RC_PINNED | (oldRefcnt SIDE_TABLE_FLAG_MASK);return true;}else {refcntStorage newRefcnt;return false;}
}
3.WeakTable弱引用表
弱引用底层调用objc_initWeak:
id objc_initWeak(id *location, id newObj)
{if (!newObj) {*location nil;return nil;}return storeWeakDontHaveOld, DoHaveNew, DoCrashIfDeallocating(location, (objc_object*)newObj);
}
storeWeak:
添加引用的时候调用storeWeak一共有五个参数其中3个参数定义在了template模版参数中HaveOld:weak指针是否指向一个弱引用HavNew:weak指针是否需要指向一个新的引用crashIfDeallocating表示被弱引用的对象是否正在析构。weak_unregister_no_lock:清除原来弱引用表中的数据weak_register_no_lock:将weak的指针地址添加到对象的弱引用表
enum CrashIfDeallocating {DontCrashIfDeallocating false, DoCrashIfDeallocating true
};// HaveOld:weak指针是否指向一个弱引用
// HavNew:weak指针是否需要指向一个新的引用
template HaveOld haveOld, HaveNew haveNew,enum CrashIfDeallocating crashIfDeallocating
static id
storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld || haveNew);if (!haveNew) ASSERT(newObj nil);Class previouslyInitializedClass nil;id oldObj;SideTable *oldTable;SideTable *newTable;// Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:if (haveOld) {//如果有拿到旧表oldObj *location;oldTable SideTables()[oldObj];} else {oldTable nil;}if (haveNew) {//如果没有创建新表newTable SideTables()[newObj];} else {newTable nil;}SideTable::lockTwohaveOld, haveNew(oldTable, newTable);if (haveOld *location ! oldObj) {//如果旧表不存在对应的objSideTable::unlockTwohaveOld, haveNew(oldTable, newTable);goto retry;}// Prevent a deadlock between the weak reference machinery// and the initialize machinery by ensuring that no // weakly-referenced object has an un-initialized isa.if (haveNew newObj) {//有新表和新对象Class cls newObj-getIsa();if (cls ! previouslyInitializedClass !((objc_class *)cls)-isInitialized()) //如果类没有初始化就重新初始化{SideTable::unlockTwohaveOld, haveNew(oldTable, newTable);class_initialize(cls, (id)newObj);// If this class is finished with initialize then were good.// If this class is still running initialize on this thread // (i.e. initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass cls;goto retry;}}// Clean up old value, if any.if (haveOld) {//如果指针曾经指向别的对象就清除// 清除原来弱引用表中数据weak_unregister_no_lock(oldTable-weak_table, oldObj, location);}// Assign new value, if any.if (haveNew) {newObj (objc_object *)// 将weak的指针地址添加到对象的弱引用表weak_register_no_lock(newTable-weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);// weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (!newObj-isTaggedPointerOrNil()) {// 将对象曾经指向过弱引用的标识置为true,没有弱引用的释放更快newObj-setWeaklyReferenced_nolock();}// Do not set *location anywhere else. That would introduce a race.*location (id)newObj;}else {// No new value. The storage is not changed.}SideTable::unlockTwohaveOld, haveNew(oldTable, newTable);// This must be called without the locks held, as it can invoke// arbitrary code. In particular, even if _setWeaklyReferenced// is not implemented, resolveInstanceMethod: may be, and may// call back into the weak reference machinery.callSetWeaklyReferenced((id)newObj);return (id)newObj;
}
weak_entry_t:
struct weak_table_t {// 弱引用数组weak_entry_t *weak_entries;// 数组个数size_t num_entries;uintptr_t mask;uintptr_t max_hash_displacement;
};
weak_entries是一个哈希数组一个对象可以被多个弱引用指针引用因此这里用数组的形式表示一个对象的多个弱引用数组中存储的内容就是弱引用对象的指针地址。当对象的弱引用个数小于等于4时走静态存储在weak_entry_t初始化的时候一并分配好大于4走动态存储。
sidetables总结
sidetables可以理解为一个全局的hash数组里面存储了sidetables类型的数据其中长度为8或者64一个objoc对象对应了一个sideTable但是一个SideTable会对应多个obj因为sidetabels的数量只有8或者64个所以有很多obj会共用一个sidetable在弱引用表中key是对象的地址value是weak指针地址的数组weak_entry_tweak_unregister_no_lock 和 weak_register_no_lock 中都是对 weak_entry_t 类型的数组进行操作_ _weak修饰对象(不会放入自动释放池)会调用objc_loadWeakRetained使得引用计数加一但仅是临时变量被引用的对象不会增加他的引用计数。ARCMRC Object-C提供了两种内存管理机制MRCMannul Reference Counting和ARCAutomatic Reference Counting为Objective-C提供了内存的手动和自动管理。
MRC
基本思想通过手动引用计数来进行对象的内存管理
涉及方法
alloc/new/copy/mutableCopy生成对象并自己持有引用计数1从0变为1retain :持有对象使对象的引用计数加1release : 释放对象使对象的引用计数减1retainCount : 获取当前对象的引用计数值autorelease : 当前对象会在autoreleasePool结束的时候,调用这个对象的release操作,进行引用计数减1dealloc : 在MRC中若调用dealloc需要显示的调用[super dealloc]来释放父类的相关成员变量
autorelease
autorelease即“自动释放”是OC的一种内存自动回收机制可以将一些临时变量通过自动释放池来回收统一释放。自动释放池销毁的时候池子里面所有的对象都会做一次release操作 那么autorelease释放与简单的release释放有什么区别呢
调用 autorelease 方法就会把该对象放到离自己最近的自动释放池中栈顶的释放池多重自动释放池嵌套是以栈的形式存取的即使对象的持有权转移给了自动释放池即注册到了自动释放池中调用方拿到了对象但这个对象还不被调用方所持有。当自动释放池销毁时其中的所有的对象都会调用一次release操作。
本质上区别在于autorelease 方法不会改变调用者的引用计数它只是改变了对象释放时机不再让程序员负责释放这个对象而是交给自动释放池去处理 。 autorelease 方法相当于把调用者注册到 autoreleasepool 中ARC环境下不能显式地调用 autorelease 方法和显式地创建 NSAutoreleasePool 对象但可以使用autoreleasepool { }块代替并不代表块中所有内容都被注册到了自动释放池中。 对于所有调用过autorelease实例方法的对象在废弃NSAutoreleasePool对象时都将调用release实例方法。 RunLoop和AutoReleasePool是通过线程的方式一一对应的 在非手动添加Autorelease pool下Autorelease对象是在当前runloop进入休眠等待前被释放的 当一个runloop在不停的循环工作那么runloop每一次循环必定会经过BeforeWaiting(准备进入休眠)而去BeforeWaiting(准备进入休眠) 时会调用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()释放旧的池并创建新池那么这两个方法来销毁要释放的对象所以我们根本不需要担心Autorelease的内存管理问题。 ARC
内存管理方案
iOS内存管理方案有
MRC和ARCTagged Pointer:专门用来处理小对象例如NSNumber、NSDate、小NSString等NONPOINTER_ISA :非指针类型的isa主要是用来优化64位地址。在 64 位架构下isa 指针是占 64 比特位的实际上只有 30 多位就已经够用了为了提高利用率剩余的比特位存储了内存管理的相关数据内容。nonpointer: 表示是否对 isa 指针开启指针优化• 0: 纯 isa 指针• 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等SideTables:散列表在散列表中主要有两个表分别是引用计数表、弱引用表。通过 SideTables()结构来实现的SideTables()结构下有很多 SideTable 的数据结构。 而 sideTable 当中包含了自旋锁引用计数表弱引用表。 SideTables()实际上是一个哈希表通过对象的地址来计算该对象的引用计数在哪个 sideTable 中。
修饰符
当ARC有效时id类型和对象类型必须附加所有权修饰符一共有如下四种。
__strong__weak__unsafe_unretained__autoreleasing
__strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。
__weak修饰符
弱引用表示并不持有对象当所引用的对象销毁了这个变量就自动设为nil。 可以利用__weak修饰符来解决循环引用问题。
__unsafe_unretained修饰符
__unsafe_unretained和__weak很像唯一区别就是__unsafe_unretained变量引用的对象再被销毁以后不会被自动设置为nil仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe此时你再尝试通过变量访问这个对象的属性或方法就会crash。一旦对象释放则会成为悬垂指针程序崩溃因此__unnsafe_unretained修饰符的变量一定要在赋值的对象存在的情况下使用。
__autoreleasing修饰符
ARC无效时使用autorelease在ARC下__autoreleasing的使用
autoreleasepool {id __autoreleasing obj [[NSObject alloc] init];
}