也谈移动端(iOS、Android)内存管理
每次谈到内存管理问题的时候,你是不是总感觉题主要开始装了?
哈哈哈,我仰天长笑,今天这里既不讲理论、也不讲实践,就从代码讲起。
内存管理?为什么内存需要管理
作为一个资深无证工程师,混迹技术圈不靠几把硬刷子怎么走江湖?
这话虽然有些江湖气,但是话糙理不糙。 如果硬说这几把硬刷子都包括什么,内存管理一定是其中之一。
相信你在写第一行代码的时候就被各种老鸟教育说注意内存,别写出爆内存的的程序。各个开发平台上也有各种Guide、Tutorial 、Best practice 文章教你如何写内存友好的程序。
抛开这些实际规则和操作不讲,你是否想过为什么内存需要管理?
经济学第一章第一节其实就在回答这个问题,经济学和管理实际上是解决紧缺资源调配的一门学科,换句话说恰恰是资源的紧缺才引入了管理。
内存也是一样,不管是在数据中心的企业级服务器还是小到你手中的移动设备,都会面临内存被用尽的时刻。
如何内存管理
说到内存管理,就不能不说说引用计数。
引用计数是将资源(如内存)的被引用次数保存起来,当被引用次数为零的时候就将其释放的过程。
Objective-C的内存管理使用的就是引用计数,不管是之前的MRC还是后来的ARC,背后的内存管理机制都是一致的,唯一不同的是MRC是将引用释放的工作交给工程师来做,而ARC是在编译阶段编译器来实现。那这里你会问了,都有编译器帮助做了,还要工程师做什么啊?毕竟只是一种机制不能保证那么智能,还是有些情景处理不好的,就比如“retain cycle”问题。
举个例子(注:以下的Objective-C代码都是在ARC的大前提下讨论):
1 | self.block = ^{ |
这里的block capture住self,而self对block又有一个强引用,这样就有了一个retain cycle,说到这里如果对于Objective-C并不是很熟悉的话可能是一脸懵逼,如果对block的capture感兴趣移步到这里 Clang block 实现手册 之 imported-variables
相信看完之后会对block有个全新的认识。
anyway 扯远了,回到刚才的retain cycle问题上来,那么问题来了,既然知道了问题的原因,下面就是如何解决的问题了。
解决的思路就是:不是形成retain cycle的两端都是强引用么?那么我们将其中的一端设置为不是强引用,这样这个retain cycle 不就破了么?
Talk is cheap, show me the code.
1 | __weak typeof(self) weakself = self; |
你会发现这里用到了一个神奇的变量标记符 __weak
既然谈到了Objective-C的内存所有权修饰符(ownership qualifier)Objective-C中共有4种内存所有权修饰符:
__strong
__weak
__autoreleasing
__unsafe_unretain
其中前三个又被称为 nontrivially ownership-qualified
由于这部分内容属于iOS开发的基础内容,网上会有一堆文章来解释,如果你仅仅只是想了解这些内容,看看这些文章就可以了(当然内容水平参差不齐,掉坑里我可不管)。如果想进一步了解这些修饰符,可以参考我GitHub上开源的这部分单元测试用例
上面是没有虚拟机的引用计数内存管理的方式,Android和Java的工程师是不是有话说?
是的,其实在这一块我们有对应的:
- strong reference
- weak reference
- soft reference
- phantom reference
如果一定要找对应关系,那么
- strong reference 对应
__strong
- weak reference 对应
__weak
__autoreleasing
声明的变量会在被包含最近的@autoreleasepool
代码域结束后就释放。
soft reference 与 weak reference 之间唯一的区别是即使某个对象的引用全部都是weak reference那么GC也不会回收,只有在内存资源紧张的时候才会回收。
虽然不是太明白 phantom reference具体应用的场景,但是仅仅就这个骚气的名字感觉会和 __unsafe_unretain
在使用上不确定内存释放时机的不确定性有的一拼。
你会问既然Objective-C会有示例的单元测试,那么Java/Android 会不会有,坦白地说,有但是没有写完全,至少在完成这篇文章之前还没完成:
可以持续关注这个GitHub工程: AndroidPlayground
特别是这个unittest
对比weak的用例后发现什么了么?
对,Java的例子多了
1 | System.gc() |
这是什么?标记回收,还要线程睡0.1秒,什么鬼,这就是虚拟机垃圾回收的工作,保证这段时间能够完成垃圾回收。
当然这里涉及到了Java的跟踪回收的垃圾回收算法,由于这部分内容别较多,所以暂时就不在这里详细讨论了。
还缺点什么
写了这么多,总觉得还缺点什么,是的,还缺少一套发现和解决这些内存泄露的方法论, 后续的文章会对如何分别在Xcode和AndroidStudio中查看内存泄露,以及定位造成内存泄露的代码给出详细的操作步骤。