iOS内存管理
引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。
结构图
下面对 SideTable 里的部分内容进行介绍
SideTables
为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面有个"s",但是并不代表它有很多个表,它其实是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。
SideTables哈希表里面还包含一个c++的map:RefcountMap, 这个是为了解决哈希冲突。
假设现在内存中有16个对象。
0x0000、0x0001、...... 0x000e、0x000f
咱们创建一个SideTables[8]来存放这16个对象,那么查找的时候发生Hash冲突的概率就是八分之一。
假设SideTables[0x0000]和SideTables[0x0x000f]冲突,映射到相同的结果。
SideTables[0x0000] == SideTables[0x0x000f] ==> 都指向同一个SideTable
苹果把两个对象的内存管理都放到里同一个SideTable中。你在这个SideTable中需要再次调用table.refcnts.find(0x0000)或者table.refcnts.find(0x000f)来找到他们真正的引用计数器。这里是一个分流。内存中对象的数量实在是太庞大了我们通过第一个Hash表只是过滤了第一次,然后我们还需要再通过这个Map才能精确的定位到我们要找的对象的引用计数器。(不是很精确的总结就是说要进行两次map的查找)
关于引用计数的类型:size_t
size_type由string类类型和vector类类型定义的类型,用以保存任意string对象或vector对象的长度,标准库类型将size_type定义为unsigned类型。size_t在32位系统上定义为 unsigned int;在64位系统上定义为 unsigned long,也就是说 size_t 在32位系统上是32位,在64位上是64位,
关于spinlock_t 锁的使用
内存中对象的数量是非常非常庞大的需要非常频繁的操作 SideTables,所以不能对整个 Hash 表加锁。苹果采用了分离锁技术。 类似于java的ConcurrentHashMap的锁分离技术,concurrenthashmap是一个非常好的map实现,在高并发操作的场景下会有非常好的效率。实现的目的主要是为了避免同步操作时对整个map对象进行锁定从而提高并发访问能力。
ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁当前需要用到的桶。试想,原来 只能一个线程进入,现在却能同时16个写线程进入(写线程才需要锁定,而读线程几乎不受限制,之后会提到),并发性的提升是显而易见的。
关于weak_table_t:
weak
id __week obj1 = obj;
那么编译器将会进行一些操作:
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak
函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰的变量的地址注册到weak表中。
如果第二参数为0,则把变量的地址从weak表中删除。
objc_storeWeak源码解析(部分代码的作用我写了注释,可以耐心读下)
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// 给旧值和新值加锁
// 按顺序锁定地址来防止锁定排序问题。
// 如果旧值在我们下面改变,请重试。
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, 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::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're 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的作用
//取消注册已注册的弱引用。当引用者的存储即将释放,但引用对象还没有死亡的时候使用。
//*@ PARAM弱表全局弱表。
//*@ PARAM引用对象。
//*@ PARAM引用弱引用。
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
//weak_register_no_lock的作用
//注册一个新的键值对(object, weak pointer),如果不存在就创建新的弱引用对象
//*@ PARAM弱表全局弱表。
//*@ PARAM引用引用弱引用指向的对象。
//*@ PARAM引用弱指针地址。
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock 如果返回nil证明这个weak值应该被拒绝
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
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::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
objc_destroyWeak
破坏弱指针和它在内部弱表中引用的对象之间的关系。如果弱指针不引用任何内容,则不需要编辑弱表。
对于弱变量的并发修改,此函数不是线程安全的。
有关一些引用计数的内容就是这些,摘取了一些其他博客的内容,整理了一下,有关weak的操作还有一些,这里就不一一贴出来了。