java和flash的垃圾回收都是一个比较热门的话题,今天我也用一个例子来测试下flash的强制垃圾回收。主要用到的而一个类是LocalConnection。
在Flash player的debug版本中提供了gc的方法
Flash Player初始化运行时,会向操作系统申请一大块内存,如果程序很小,有可能根本用不了这么多内存,但FP在开始时不考虑这些,大多数情况下,第一次申请的内存总是不够用的。第一次申请的内存大小,与操作系统、浏览器环境有关。
当Flash Player发现已经申请的内存不够用时,它会再向操作系统申请一大块内存。但在申请之前,请注意,FP会尝试进行垃圾内存回收。那么它是如何回收的呢?
Flash Player在内部使用懒惰式引用计数回收方案进行垃圾内存回收。
懒惰式指:FP并不会一次把所有可以回收的对象全部回收,它一次仅会回收一部分,如果内存不够用,它会向操作系统申请,如果系统无内存了,它会再次回收,如果全部回收了仍不够用,Game Over!
引用计数指:FP在内部给每个对象标记一个记号,当没有任何对象引用此对象时,它即是可以被回收的;如果一个容器内有许多相互关联的对象,当把这个容器从显示列表中移除,并且置为null后,它也是可以被回收的。
在清楚了FP的内部垃圾回收机制之后,我们可以创建两次没用的LocalConnection,并且连接并不存在的地址,故意抛出异常然后捕获,就强制垃
圾回收,因为,在AS3中LocalConnection是比较占用内存的对象,两次创建该类对象并尝试进行连接的内存开销大小足以请Flash
Player重新向操作系统申请内存,而在申请之前,FP会尝试回收。原理即是这么简单,非独使用LocalConnection可以,其它较耗内存的对
象也可以。
下面是我写的测试代码,主要测试三中情况下内存的使用情况。
- package
- {
- import flash.display.Sprite;
- import flash.external.ExternalInterface;
- import flash.net.LocalConnection;
- public class MemeryGcTest extends Sprite
- {
- private const num:int = 30000; //子元素个数 根据自己的电脑配置来设置
- private var parentContainer:Sprite;//父容器
- private var childrenRect:Array;//所有子元素的引用
- public function MemeryGcTest(){
- init();
- }
- private function init() : void{
- parentContainer=new Sprite();
- addChild(parentContainer);
- createAllChildrens();
- removeAllchildrens();
- setChildrenNull();
- //doGc();
- }
- /**
- * 移除所有对象
- *
- */
- private function removeAllchildrens():void {
- removeAllChildrens( );
- removeChild(parentContainer);
- }
- /**
- * 设置不用对象为null 否则不会进行垃圾回收
- *
- */
- private function setChildrenNull():void{
- childrenRect = null;
- parentContainer = null;
- }
- /**
- * 创建所有子元素
- *
- */
- private function createAllChildrens() : void {
- childrenRect=new Array();
- for(var i:int = 0;i<num; i++){
- var sprite:Sprite=new Sprite();
- childrenRect.push(sprite);
- sprite.graphics.beginFill(0xff0000);
- sprite.graphics.drawRect(0+i/50,0,100,100);
- sprite.graphics.endFill();
- parentContainer.addChild(sprite);
- }
- }
- /**
- * 移除所有子元素
- *
- */
- private function removeAllChildrens():void{
- for(var i:int=0;i<num;i++){
- parentContainer.removeChild(childrenRect[i]);
- delete childrenRect[i];
- }
- }
- /**
- * 强制垃圾回收
- *
- */
- private function doGc():void{
- try{
- var conn1:LocalConnection= new LocalConnection();
- conn1.connect("testGc");
- var conn2:LocalConnection= new LocalConnection();
- conn2.connect("testGc");
- }catch(error:Error){
- conn1 = null;
- conn2 = null;
- }
- }
- }
- }
第一种情况,不设置不用元素为null和强制垃圾回收
- private function init() : void{
- parentContainer=new Sprite();
- addChild(parentContainer);
- createAllChildrens();
- removeAllchildrens();
- //setChildrenNull();
- //doGc();
测试结果如下:
可以看出占用的内存比较高,点击GC按钮内存依然是“居高不下”。
第二种情况,设置不用元素为null但不强制执行垃圾回收。
- private function init() : void{
- parentContainer=new Sprite();
- addChild(parentContainer);
- createAllChildrens();
- removeAllchildrens();
- setChildrenNull();
- //doGc();
测试结果如下:
设置null后虽然刚开始峰值很高,但是Flash Player执行了垃圾回收,很快内存下降到12K。
第三种情况,设置不用对象为null并强制进行垃圾回收。
- private function init() : void{
- parentContainer=new Sprite();
- addChild(parentContainer);
- createAllChildrens();
- removeAllchildrens();
- setChildrenNull();
- doGc();
测试结果如下:
可以看出强制垃圾回收确实执行了。
内存泄露举例:
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来对项目进程进行监控,可知道历史创建过哪些对象,目前有哪些对象没有被删除,创建的数量,占用的内存比例和用量,创建过程等信息。
总结:关键还是要做好清除工作,自己设置的引用自己要记得删除,自己用过的系统类要记得做好回收处理工作。 以上问题解决的好的话不需要自定义强制回收器也有可能被系统正常的自动回收掉。