Flash Player的garbage collection(GC)分两种运行方式,一种是“引用计数法”(Reference Counting),一种是“标记-清除法”(Mark Sweeping)。
引用计数法是通过计算指向某个对象的引用的数量来确定是否清除该对象。如果一个对象的引用数量为0,表示程序无法再访问到该对象,则清除该对象;如果引用计数不为0,则不清除。这种方法运行代价较小,但是这种方法无法清除存在循环引用关系的对象集合。标记-清除法是从程序的根对象开始,遍历每个引用指向的对象。遍历经过的对象,则将其标记。最后清除所有没有打上标记的对象。这种方法比较彻底,但是运行代价较高。
FlashPlayer运行GC的时间并不固定,它会根据你的内存的占用情况来决定运行GC的时间。它会根据用户机器的内存值来设定一个阀值,然后将程序的占用内存量保存在该阀值左右。
正因为FlashPlayer这种“不确定”的GC机制,所以我们所要做的主要工作是确保创建的对象在不需要的时候可以被释放。确保对象可以被释放的大原则是没有外部引用指向该对象,除了一般情况下的没有将外部引用显示地设为null之外,以下的情况也会导致对象无法释放:
- 没有remove监听的事件。比如,A对象对某个事件进行监听,监听函数(Event Handler)存在于B对象中,则相当于A对象会保存一个B对象的方法的引用,会导致B对象的内存无法释放。
解决方法:注意remove掉监听事件;或者在调用addEventListener()时,将监听函数设为弱引用,但这种做法只适合一次性的监听。
- 使用BindingUtils.bindSetter()、ChangeWatcher.watch()绑定某个对象之后,没有清除该绑定。道理同1,其实绑定某个对象,也就是监听其发出的PropertyChange事件。
解决方法:使用ChangeWatcher.unwatch()来清除绑定关系。
- 声明了样式,并在样式中使用了嵌入式资源。比如在标签中定义了样式名称。一个对象定义了样式,相当于对外声明了一个全局可用的样式,因此会到导致外部保存了该对象的引用,可能导致对象无法释放。
解决方法:解决方法很多,可以使用动态加载的样式,或者使用一个类或模块(Module)专门管理样式,这些解决方法取决程序的架构设计。
- 使用ExternalInface.callBack()声明了对外的API函数。类似于情况1,一个对象对外声明了API,就使外部保存了指向该对象的引用。
解决方法:如果之前使用了ExternalInface.callBack("APIName", functionName)声明了一个API,则可以使用ExternalInface.callBack("APIName", null)取消该API。
- 某些控件(类似TextInput),或者由这类控件构成的自定义组件,当焦点在这些控件上时,即使从DisplayList移除掉这些控件并删除引用,这些控件对象也无法释放。这个问题还有人提出来是一个Bug(http://bugs.adobe.com/jira/browse/SDK-14781)。这个问题估计和flash的焦点管理机制有关。
解决方法:目前的解决方法只能是等焦点重新转移到其他控件上(比如点击了其他控件),如此之前的控件对象就可以被GC释放。
那应该在什么时候做好垃圾清理的准备工作呢?之前有的文章说应该监听组件的removeFromStage事件,在其处理方法中进行垃圾清理的准备工作(清除引用,删除监听器,清除绑定关系,取消对外API等工作)。
其实这种方法不太确切。因为removedFromStage事件是当组件从DisplayList上移除的时候发出的,并不代表该组件对象的生命周期已经终结。只要程序保留了该组件对象的引用,可以再重新把该组件对象添加到DisplayList上(此时,该组件对象会发出addedToStage事件)。如果单纯在removedFromStage事件的监听函数中做该对象的垃圾清理准备工作,当组件重新被使用的时候,可能导致该组件对象原来的状态被破坏而无法使用。
因此,比较好的实践方法应该是,利用addedToStage、removedFromStage两个事件的对应关系,在removedFromStage事件的处理方法中执行垃圾清理的准备工作(清除引用,删除监听器,清除绑定关系,取消对外API等作),而在addedToStage事件的处理方法中执行removedFromStage事件的处理方法的反操作(设置引用、添加监听、设定绑定关系、设置API...也可以认为是一个组件对象的初始化操作),这样就可以保证一个组件对象被从DisplayList上移除后,可以释放相应内存;如果保存其引用,并将其重新添加到DisplayList上,又可重新使用。
最后翻译一段关于内存清理的建议:
- usage of instance members instead of static members can easily be detected with the profiler (replace by static members where possible)
使用实例成员(instance members),而不是用静态成员(static members),可以更容易地被profiler检查到。因