flash 垃圾回收浅谈

在Flash Player初始化运行时,会向操作系统申请一大块内存,如果程序很小,有可能根本用不了这么多内存,但FP在开始时不考虑这些,大多数情况下,第一次申请的内存总是不够用的。第一次申请的内存大小,与操作系统、浏览器环境有关。
当Flash Player发现已经申请的内存不够用时,它会再向操作系统申请一大块内存。但在申请之前,请注意,FP会尝试进行垃圾内存回收。那么它是如何回收的呢?

Flash Player在内部使用懒惰式引用计数回收方案进行垃圾内存回收,懒惰式指:FP并不会一次把所有可以回收的对象全部回收,它一次仅会回收一部分,如果内存不够用,它会向操作系统申请,如果系统无内存了,它会再次回收,如果全部回收了仍不够用,Game Over!引用计数指:FP在内部给每个对象标记一个记号,当没有任何对象引用此对象时,它即是可以被回收的;如果一个容器内有许多相互关联的对象,当把这个容器从显示列表中移除,并且置为null后,它也是可以被回收的。
FlashPlayer运行GC的时间并不固定,它会根据你的内存的占用情况来决定运行GC的时间。它会根据用户机器的内存值来设定一个阀值,然后将程序的占用内存量保存在该阀值左右。
正因为FlashPlayer这种“不确定”的GC机制,所以我们所要做的主要工作是确保创建的对象在不需要的时候可以被释放。确保对象可以被释放的大原则是没有外部引用指向该对象,除了一般情况下的没有将外部引用显示地设为null之外,以下的情况也会导致对象无法释放:

1. 没有remove监听的事件。比如,A对象对某个事件进行监听,监听函数(Event Handler)存在于B对象中,则相当于A对象会保存一个B对象的方法的引用,会导致B对象的内存无法释放。
解决方法:注意remove掉监听事件;或者在调用addEventListener()时,将监听函数设为弱引用,但这种做法只适合一次性的监听。

2. 使用BindingUtils.bindSetter()、ChangeWatcher.watch()绑定某个对象之后,没有清除该绑定。道理同1,其实绑定某个对象,也就是监听其发出的PropertyChange事件。
解决方法:使用ChangeWatcher.unwatch()来清除绑定关系。

3. 声明了样式,并在样式中使用了嵌入式资源。比如在<mx:Style>标签中定义了样式名称。一个对象定义了样式,相当于对外声明了一个全局可用的样式,因此会到导致外部保存了该对象的引用,可能导致对象无法释放。
解决方法:解决方法很多,可以使用动态加载的样式,或者使用一个类或模块(Module)专门管理样式,这些解决方法取决程序的架构设计。

4. 使用ExternalInface.callBack()声明了对外的API函数。类似于情况1,一个对象对外声明了API,就使外部保存了指向该对象的引用。
解决方法:如果之前使用了ExternalInface.callBack("APIName", functionName)声明了一个API,则可以使用ExternalInface.callBack("APIName", null)取消该API。

5. 某些控件(类似TextInput),或者由这类控件构成的自定义组件,当焦点在这些控件上时,即使从DisplayList移除掉这些控件并删除引用,这些控件对象也无法释放。这个问题还有人提出来是一个Bug(http://bugs.adobe.com/jira/browse/SDK-14781)。这个问题估计和flash的焦点管理机制有关。
那应该在什么时候做好垃圾清理的准备工作呢?之前有的文章说应该监听组件的removeFromStage事件,在其处理方法中进行垃圾清理的准备工作(清除引用,删除监听器,清除绑定关系,取消对外API等工作)。
其实这种方法不太确切。因为removedFromStage事件是当组件从DisplayList上移除的时候发出的,并不代表该组件对象的生命周期已经终结。只要程序保留了该组件对象的引用,可以再重新把该组件对象添加到DisplayList上(此时,该组件对象会发出addedToStage事件)。如果单纯在removedFromStage事件的监听函数中做该对象的垃圾清理准备工作,当组件重新被使用的时候,可能导致该组件对象原来的状态被破坏而无法使用。
因此,比较好的实践方法应该是,利用addedToStage、removedFromStage两个事件的对应关系,在removedFromStage事件的处理方法中执行垃圾清理的准备工作(清除引用,删除监听器,清除绑定关系,取消对外API等作),而在addedToStage事件的处理方法中执行removedFromStage事件的处理方法的反操作(设置引用、添加监听、设定绑定关系、设置API...也可以认为是一个组件对象的初始化操作),这样就可以保证一个组件对象被从DisplayList上移除后,可以释放相应内存;如果保存其引用,并将其重新添加到DisplayList上,又可重新使用。
垃圾回收的一些知识总结:
1、被删除对象在外部的所有引用一定要被删除干净才能被系统当成垃圾回收处理掉。
2、父对象内部的子对象被外部其他对象引用了,会导致此子对象不会被删除,子对象不会被删除又会导致了父对象不会被删除。
3、如果一个对象中引用了外部对象,当自己被删除或者不需要使用此引用对象时,一定要记得把此对象的引用设置为null。
4、本对象删除不了的原因不一定是自己被引用了,也有可能是自己的孩子被外部引用了,孩子删不掉导致父亲也删不掉。
5、除了引用需要删除外,系统组件或者全局工具、管理类如果提供了卸载方法的就一定要调用删除内部对象,否则有可能会造成内存泄露和性能损失。
6、父对象立刻被删除了不代表子对象就会被删除或立刻被删除,可能会在后期被系统自动删除或第二次移除操作时被删除。
7、如果父对象remove了子对象后没有清除对子对象的引用,子对象一样是不能被删除的,父对象也不能被删除。
8、注册的事件如果没有被移除不影响自定义的强行回收机制,但有可能会影响正常的回收机制,所以最好是做到注册的事件监听器都要记得移除干净。
9、父对象被删除了不代表其余子对象都删除了,找到一种状态的泄露代码不等于其他状态就没有泄露了,要各模块各状态逐个进行测试分析,直到测试任何状态下都能删除整个对象为止。
10、当触发了某个event后,不再使用的话,请将其remove掉。
11、能不使用Effect就不要使用Effect。

内存泄露举例:
1、引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null。
2、系统类泄露:使用了系统类而忘记做删除操作了,如BindingUtils.bindSetter(),ChangeWatcher.watch()函数时候完毕后需要调用ChangeWatcher.unwatch()函数来清除引用 ,否则使用此函数的对象将不会被删除; 类似的还有MUSIC,VIDEO,IMAGE,TIMER,EVENT,BINDING等。
3、效果泄露:当对组件应用效果Effect的时候,当本对象本删除时需要把本对象和子对象上的Effect动画停止掉,然后把Effect的target对象置null; 如果不停止掉动画直接把 Effect置null将不能正常移除对象。
4、SWF泄露:要完全删除一个SWF要调用它的unload()方法并且把对象置null。
5、图片泄露:当Image对象使用完毕后要把source置null。
6、声音、视频泄露: 当不需要一个音乐或视频是需要停止音乐,删除对象,引用置null。

内存泄露解决方法:
1. 在组件的REMOVED_FROM_STAGE事件回掉中做垃圾处理操作(移除所有对外引用(不管是VO还是组件的都需要删除),删除监听器,调用系统类的清除方法) 先remove再置null, 确保被remove或者removeAll后的对象在外部的引用全部释放干净。
2. 利用Flex的性能优化工具Profile来对项目进程进行监控,可知道历史创建过哪些对象,目前有哪些对象没有被删除,创建的数量,占用的内存比例和用量,创建过程等信息。

总结:关键还是要做好清除工作,自己设置的引用自己要记得删除,自己用过的系统类要记得做好回收处理工作。 以上问题解决的好的话不需要自定义强制回收器也有可能被系统正常的自动回收掉。

众所周知,由于Flash Player的垃圾回收机制是自动进行的,因此就算是上述内容的内容都符合要求,那么还是会产生内存“高居不下”的情况。
因此,我接下来介绍一个非常规的方式,让Flash Player的垃圾回收机制在我的控制之中。(以下的内容也不是我首创的,但是特此总结说明一下)

强制垃圾回收:(即著名的hack方式)
通过故意让SWF在运行时出错,然后throw出错误,而同时通过catch error来继续运行SWF文件。而垃圾回收机则会在SWF抛出错误的时候,被强制执行一次,以清除内存中无效的数据占用,减少资源的消耗。

下面是我找到一个通过这种hack方式处理垃圾回收的代码:


package util
{
import flash.net.LocalConnection;
import flash.system.System;

public class Memory {

public function Memory() {
//TO DO
}
public static function gc() : void {
try {
new LocalConnection().connect( 'foo' );
new LocalConnection().connect( 'foo' );
} catch ( e : * ) {}
}
public static function get used() : Number {
return System.totalMemory;
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值