我们都知道:
- 以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?
子线程为空的情况:
子线程有可能延迟释放对象的情况:
######## POOL 相当于POOL_BOUNDARY
可以看到子线程默认就有autoreleasePool, 不需要手动创建, [Test object]和[NSArray array]没有延迟释放, 看汇编是被优化掉了, [NSString stringWithFormat:]的非taggedpointer对象延迟释放了
子线程的autoreleasePool何时销毁?
子线程默认不开启runloop, 所以跟主线程的销毁方式不同
监听变量的赋值过程查看堆栈, 可以看到[NSThread exit] -> [pthread exit] -> [_pthread_tsd_cleanup] -> [tls dealloc] -> objc_autoreleasePoolpop -> 对象的dealloc
子线程中pool是在线程销毁时从TLS中销毁的, TLS是Thread Local Storage
如果线程需要保活, 就可能导致无法释放, 后果可想而知...
page销毁的源码细节
在之前的demo中就发现有的时候pool中会有个空的page, 当时没有在意, 直到看了源码这段:
page是延迟销毁的, 在当前page里对象少于一半时才销毁child, 应该是为了避免在临界值附近频繁创建销毁page造成性能浪费
验证下
类似的, JDK1.8的HashMap在哈希冲突时会做链表和红黑树的切换, 8个时链表转为红黑树, 6个时红黑树转为链表, 也是为了避免临界值附近时的频繁切换
仔细想想其实苹果还有很多这样的细节设计, 比如引用计数在isa中和SideTables中的管理, 比如weak引用在定长数组和哈希表中的切换
关于Objc_retainAutoreleasedReturnValue和Objc_autoreleaseReturnValue
《Objective-C高级编程iOS与OS X多线程和内存管理》里的描述:
苹果官网文档里的描述:
llvm文档里的描述:
系统会尽最大努力做优化, 测下来并没有固定规律, 主线程和子线程的逻辑有所不同, iOS和MacOS的逻辑也有所不同, autorelease对象数量也会影响结果
最后的小建议
子线程中默认有pool, 但是不管是主线程或子线程, 有大量临时变量(可能延迟释放)的情况下最好是手写个pool, 这样在pool的作用域结束时就会pop, 避免内存暴涨或内存泄漏
不要在viewDidLoad中创建过多的延迟释放对象, 你会发现viewDidAppear会延迟调用
NSLog做打印有时候有毒被坑过, 尽量断点调试