关于AS3的垃圾回收
FlashPlayer运行GC(Gabage Collection)的时间并不固定,它会根据你的内存的占用情况来决定运行GC的时间。它会根据用户机器的内存值来设定一个阀值,然后将程序的占用内存量保存在该阀值左右。
GC是在每次申请内存时,根据当前内存占用来触发的。申请内存是一个必要因素。所以,如果一直不进行申请内存的操作,就算内存达到了一个高值,它也不会进行GC。
但要注意,这只是决定回收的时机,而不是回收的内容。这个延迟执行内存回收也就是个表面的现象,不管什么时候执行GC,能够回收的内存最终都能回收,不能回收的肯定不能回收。唯一的影响是,因为回收是延迟执行的,你在查看内存的时候不能直观地看到因为一个对象被废弃而回收内存的过程,会产生迷惑。这对于解决内存泄露是无关紧要的。
内存泄露指的就是当你销毁了一个对象的时候,它占用的内存却无法被回收,这会导致可用内存越来越小最终溢出,在内存紧张的环境中将会造成系统崩溃。
在debug版本下可使用
System.gc()
来强制回收内存,但是在release版本并不起作用。可以使用如下代码
try { import flash.net.LocalConnection; var conn1:LocalConnection = new LocalConnection (); var conn2:LocalConnection = new LocalConnection (); conn1.connect("gc"); conn2.connect("gc"); }catch(e:Error){}
调用 GC 仅仅是用于测试,实际产品中调用 GC 基本没有意义(除了用于控制 GC时机),如果程序中出现了内存泄露,和 GC 并没有关系。
静态属性是一个特殊的情况。静态属性本身就是根,所以你必须将其设置 null 才有可能被回收,没有别的办法。
弱引用会改变垃圾回收的规则。如果使用了弱引用,addEventListener 将不会影响对象回收,即使对 stage
添加监听,也不会导致自己被回收。
• 一处是 addEventListener 的第5个属性,名为 userWeakReference,设置为true,监听事件将不会影响对象回收。
• 一处是 Dictionary 的构造函数参数,名为 weakKeys,设置为 true,当键为复杂对象时,即使Dictionary 存在,键依然可以被回收。注意,这里说的是键,不是值,值是不享受弱引用待遇的。这个属性也写得也很明白,是weakKeys。
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检查到。因此,尽可能地使用实例成员,而不要用静态成员。
- usage of weak references and / or removal of eventListeners after consumption of the event (if posible) helps reducing the memory usage
在事件完成之后,将其设为引用 而且/或者(and / or) 将其remove掉,有助于减少内存使用。
- moduleLoader.unloadModule leaks memory, use moduleLoader.url=null instead
moduleLoader.unloadModule()会导致内存泄露,因此建议使用将moduleLoader.url=null.
- module memory is freed at arbitrary times (not at unload)
module内存的释放时间是不确定(并不是在unload的时候)。
- runnning debug version of modules leaks huge amounts of memory no matter which container is used
使用debug版本的module会导致大量的内存泄露,不管其容器是否使用。
- declaring modules as modules in the configuration of a flex builder 3 project (and not as applications like in FlexBuilder 2) and optimizing for a specific application reduces module size drastically
将一个程序块声明为module,而不要将其声明为application,并且设置各module专门为一个application进行优化,能大量节约内存。
- forcing garbageCollection (double LocalConnection.connect hack) is necessary in order to measure leaks and to keep memory under control 在适当的时候,为了内存可控,可强制使用垃圾收集器(garbageCollection),方法如下:
try { import flash.net.LocalConnection; var conn1:LocalConnection = new LocalConnection (); var conn2:LocalConnection = new LocalConnection (); conn1.connect("gc"); conn2.connect("gc"); }catch(e:Error){}
- 使用release版的module swf。use the release version of the module swf
- uninstall the debug flash player ("uninstall_flash_player.exe")
卸载debug版的flash player。
- install the release version of the flash player ("install_flash_player_active_x.msi")
安装release版的flash player。