子线程的内存管理

1,333 阅读3分钟

我们都知道:

  • 以alloc, new, copy, mutableCopy开头并且遵循驼峰命名法的方法创建的对象系统会在合适的地方做release操作, 而其他类方法命名的方法创建的对象比如[Test object], [NSArray array], [NSString stringWithFormat:]的非taggedpointer,系统会追加autorelease而可能延迟释放, 也可能被Objc_retainAutoreleasedReturnValue和Objc_autoreleaseReturnValue的组合调用优化而不延迟释放
  • 对于主线程系统的autoreleasePool, 是在runloop的beforeWaiting时做pop操作去发送release消息, page本身是栈结构, page之间是双向链表结构, pool嵌套的情况下会共用没有满的page并以POOL_BOUNDARY做分割 (呀~ beforeWaiting真是个繁忙的时刻, 还要处理UI操作等等)

子线程是否有autoreleasePool?

子线程为空的情况:

1.png

子线程有可能延迟释放对象的情况:

2.png

3.png

######## POOL 相当于POOL_BOUNDARY

可以看到子线程默认就有autoreleasePool, 不需要手动创建, [Test object]和[NSArray array]没有延迟释放, 看汇编是被优化掉了, [NSString stringWithFormat:]的非taggedpointer对象延迟释放了

12.png

子线程的autoreleasePool何时销毁?

子线程默认不开启runloop, 所以跟主线程的销毁方式不同

4.png

5.png

监听变量的赋值过程查看堆栈, 可以看到[NSThread exit] -> [pthread exit] -> [_pthread_tsd_cleanup] -> [tls dealloc] -> objc_autoreleasePoolpop -> 对象的dealloc

子线程中pool是在线程销毁时从TLS中销毁的, TLS是Thread Local Storage

如果线程需要保活, 就可能导致无法释放, 后果可想而知...

page销毁的源码细节

8.png

在之前的demo中就发现有的时候pool中会有个空的page, 当时没有在意, 直到看了源码这段:

9.png

page是延迟销毁的, 在当前page里对象少于一半时才销毁child, 应该是为了避免在临界值附近频繁创建销毁page造成性能浪费

验证下

10.png

11.png

类似的, JDK1.8的HashMap在哈希冲突时会做链表和红黑树的切换, 8个时链表转为红黑树, 6个时红黑树转为链表, 也是为了避免临界值附近时的频繁切换

仔细想想其实苹果还有很多这样的细节设计, 比如引用计数在isa中和SideTables中的管理, 比如weak引用在定长数组和哈希表中的切换

关于Objc_retainAutoreleasedReturnValue和Objc_autoreleaseReturnValue

《Objective-C高级编程iOS与OS X多线程和内存管理》里的描述:

13.png

苹果官网文档里的描述:

14.png

llvm文档里的描述:

15.png

16.png

系统会尽最大努力做优化, 测下来并没有固定规律, 主线程和子线程的逻辑有所不同, iOS和MacOS的逻辑也有所不同, autorelease对象数量也会影响结果

最后的小建议

子线程中默认有pool, 但是不管是主线程或子线程, 有大量临时变量(可能延迟释放)的情况下最好是手写个pool, 这样在pool的作用域结束时就会pop, 避免内存暴涨或内存泄漏

不要在viewDidLoad中创建过多的延迟释放对象, 你会发现viewDidAppear会延迟调用

NSLog做打印有时候有毒被坑过, 尽量断点调试