内存管理

116 阅读3分钟
  1. 内存布局
  2. 内存管理方案
  3. 数据结构
  4. ARC & MRC
  5. 引用计数
  6. 弱引用
  7. 自动释放池
  8. 循环引用

内存布局

image.png

  • stack: 方法调用
  • heap: 通过alloc等分配的对象
  • bss: 未初始化的全局变量等
  • data: 已初始化的全局变量等
  • text: 程序代码

内存管理方案

  • TaggedPointer(小对象)
  • NONPOINTER_ISA
  • 散列表(弱引用表和引用计数表)

NONPOINTER_ISA arm64架构 image.png

image.png

散列表 SideTables()结构

image.png sideTable结构

image.png

为什么不是一个SideTable? image.png 分离锁 image.png 怎样实现快速分流? SideTables的本质是一张Hash表。

image.png

Hash查找 例:给定值是对象内存地址,目标值是数组下标索引。

image.png

数据结构

  • Spinlock_t
  • RefcountMap
  • weak_table_t

Spinlock_t

  • Spinlock_t是”忙等“的锁
  • 适用于轻量访问

RefcountMap(哈希表提高查找效率)

image.png size_t

image.png

weak_table_t(弱引用表)

image.png

ARC & MRC

MRC(手动引用计数) image.png

ARC(自动引用计数)

  • ARC是LLVM和Runtime协作的结果。
  • ARC中禁止手动调用retain/release/retainCount/dealloc
  • ARC中新增weak、strong属性关键字

引用计数管理

实现原理分析

  • alloc
  • retain
  • release
  • retainCount
  • dealloc

alloc实现 经过一系列调用,最终调用了C函数calloc 此时并没有设置引用计数为1

retain实现 SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; refcntStorage += SIDE_TABLE_RC_ONE;(加上了对应的偏移量)

release实现 SideTable& table = SideTables()[this]; RefcontMap::iterator it = table.refcnts.find(this); it->second -= SIDE_TABLE_RC_ONE;

retainCount实现 SideTable& table = SideTables()[this]; size_t refcnt_result = 1; RefcontMap::iterator it = table.refcnts.find(this); refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;

dealloc实现(重要)

image.png

object_dispose()实现

image.png

objc_destructInstance()实现

image.png

clearDeallocating()实现

image.png

弱引用管理

image.png 添加weak变量

image.png

清除weak变量,同时设置指向为nil

image.png

自动释放池

image.png array何时释放?

  • AutoreleasePool的实现原理是怎样的?
  • AutoreleasePool为何可以嵌套使用?

编译器会将@autoreleasepool()改写为: void *ctx = objc_autoreleasePoolPush(); {}中的代码 objc_autoreleasePoolPop(ctx);

objc_autoreleasePoolPush的实现

image.png

objc_autoreleasePoolPop的实现

image.png

自动释放池的数据结构

  • 是以栈为结点通过双向链表的形式组合而成。
  • 是和线程一一对应的。

双向链表

image.png

image.png

AutoreleasePoolPage

image.png

image.png

AutoreleasePoolPage::Push

image.png

[obj autorelease]

image.png

image.png

AutoreleasePoolPage::Pop

  • 根据传入的哨兵对象找到对应的位置
  • 给上次push操作之后添加的对象依次发送release消息
  • 回退next指针到正确位置

image.png

image.png 清除之后指向正确的位置,上面两图。

自动释放池的总结

  • 在当次runloop将要结束的时候调用AutoreleasePoolPage::Pop()。
  • 多层嵌套就是多次插入哨兵对象。
  • 在for循环中alloc图片数据等内存消耗比较大的场景手动插入autoreleasePool.

循环引用

  • 自循环引用
  • 相互循环引用
  • 多循环引用

自循环引用

image.png

相互循环引用

image.png

多循环引用

image.png

考点

  • 代理
  • Block
  • NSTimer
  • 大环引用

如何破除循环引用?

  • 避免产生循环引用
  • 在合适的时机手动断环

破除循环引用具体的解决方案有哪些?

  • __weak
  • __block
  • __unsafe_unretained

__weak破解

image.png

__block破解(ARC与MRC有区别)

  • MRC下,__block修饰的对象不会增加其引用计数,避免了循环引用
  • ARC下,__block修饰的对象会被强引用,无法避免循环引用,需手动解环

__unsafe_unretained(不建议用)

  • 修饰对象不会增加引用计数,避免了循环引用
  • 如果被修饰的对象在某一时机被释放,会产生悬垂指针。

循环引用示例

  • Block的使用示例
  • NSTimer使用示例

NSTimer的循环引用问题

image.png

解决方案

image.png

源码部分

image.png

image.png

内存管理总结

  • 什么是ARC?
  • 为什么weak指针指向的对象在废弃之后会被自动置为nil?
  • 苹果是如何实现AutoreleasePool的?
  • 什么是循环引用?你遇到过哪些循环引用,是怎样解决的?