谈谈ActionScript垃圾回收_OpenStack, 云计算等杂项讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  OpenStack, 云计算等杂项讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 5170 | 回复: 0   主题: 谈谈ActionScript垃圾回收        上一篇   下一篇 
    本主题由 koei 于 2012-2-15 19:28:36 移动
barry
注册用户
等级:中校
经验:1534
发帖:236
精华:2
注册:2012-1-13
状态:离线
发送短消息息给barry 加好友    发送短消息息给barry 发消息
发表于: IP:您无权察看 2012-2-10 10:29:14 | [全部帖] [楼主帖] 楼主

在《给AS程序员的一点建议一文》中我提到了释放资源的重要性。最近在一些项目过程中我又对这方面有了更多的理解,在此希望能够分享给大家。首先让我们来回顾一下关于垃圾回收(Garbage Collection,下文简称GC)的一些知识。要阅读本文,你需要对GC机制有些基本认识。

在ActionScript中,我们没有API可以直接删除一个对象,也不能控制Player进行GC。但是GC的行为是可以预估的,作为开发者,我们需要了解的是GC执行的时机是发生在需要向操作系统请求分配内存的时候。

北京联动北方科技有限公司

从上面的模拟图我们可以看到:

  • Player以块的方式请求和释放内存。GC的结果不一定就是更少的内存占用,也有可能是从操作系统获得更多的可用内存。
  • Player会在某些GC过程中把内存中未使用部分组合成可以释放的块还给操作系统。
  • 此外还要注意的是Player为了避免占用太多的CPU资源,会将一些GC操作分到不同的时间片中运行,所以一次GC过程并不一定清理完所有可回收资源。

一次GC过程(GC Pass)分为以下两个步骤:

Reference Counting


统计所有对象的引用计数,如果某个对象没有任何引用,就标记为可回收。

北京联动北方科技有限公司

这个操作很好理解,需要强调的是weak reference(弱引用)是不参与计算的。引用计数是一个相对省CPU的操作,能够筛选出大部分可回收资源,但是对一些循环引用的情况就无能为力了。在下图中,标记为绿色的对象每个的引用数都为1,但它们明显是应该被回收的。

北京联动北方科技有限公司

所以GC需要进行第二个步骤:

Mark Sweeping


这个步骤是从根对象(Root)开始轮询对象的引用。所谓的根对象包括:

  • Stage对象
  • 静态变量
  • 局部变量

这种方式足够精确,能够成功筛选出上图中绿色标记的对象,而它的代价就是较大的计算开销。

为了帮助GC过程更高效的执行,最好是能在第一步引用计数中就把需要回收的对象都标记出来。具体的做法就是把所有不需要的对象引用全部清空,包括:

  • 删除成员变量的引用
  • 从可视对象列表上移除对象
  • 移除事件监听

难点:事件监听是否会造成对象不能回收?这个问题要具体分析,有些情况可以,有些情况却不可以。归根结底还是引用关系的问题。来看下面这个例子:

Actionscript代码
北京联动北方科技有限公司

  1. Foo.as: 
  2.  
  3. public class Foo extends Sprite 
  4.        private var bar:Sprite; 
  5.        public function Foo() 
  6.        { 
  7.              
  8.              //监听bar发出的事件。可以看作是bar引用了foo,因为foo的引用被保存在bar的监听者数组里。 
  9.              bar.addEventListener(MouseEvent.CLICK, clickHandler); 
  10.              addChild(bar); 
  11.        } 
  12.        
  13.        private function clickHandler(event:MouseEvent):void 
  14.        { 
  15.              ... 
  16.        } 


Foo.as:

public class Foo extends Sprite
{
       private var bar:Sprite;
       public function Foo()
       {

             //监听bar发出的事件。可以看作是bar引用了foo,因为foo的引用被保存在bar的监听者数组里。
             bar.addEventListener(MouseEvent.CLICK, clickHandler);
             addChild(bar);
       }

       private function clickHandler(event:MouseEvent):void
       {
             ...
       }
}



Actionscript代码
北京联动北方科技有限公司

  1. Main.as:   
  2.     
  3. // 创建foo实例   
  4. var foo:Foo = new Foo();   
  5. addChild(foo);   
  6.     
  7. // do something with foo...   
  8.     
  9. // 清除foo的引用   
  10. removeChild(foo);   
  11. foo = null;  


Main.as:

// 创建foo实例
var foo:Foo = new Foo();
addChild(foo);

// do something with foo...

// 清除foo的引用
removeChild(foo);
foo = null;

我们看到foo引用了bar,而bar又通过事件监听的联系引用了foo,这就构成了一个循环引用。根据前文对GC步骤的分析,这两个对象都必须到第二步mark and sweep才能被标记出来。

如果我们对事件监听用弱引用的方式:

Actionscript代码
北京联动北方科技有限公司

  1. bar.addEventListener(MouseEvent.CLICK, clickHandler, false, 0, true);  


bar.addEventListener(MouseEvent.CLICK, clickHandler, false, 0, true);

由于弱引用不计入引用计数,所以现在foo的引用数为0。GC在第一步操作中就能把foo标记出来,从而减少了一些运算开销。这也是为什么Grant Skinner呼吁把弱引用作为事件监听的默认方式的原因。

但是作为最佳实践,我们还是提倡要手动移除事件监听。以下代码添加一个destroy()方法(也有习惯命名为dispose()或kill()等)到Foo对象中:

Actionscript代码
北京联动北方科技有限公司

  1. public function destroy():void 
  2.        bar.removeEventListener(MouseEvent.CLICK, clickHandler); 
  3.        removeChild(bar); 
  4.        bar = null; 


public function destroy():void
{
       bar.removeEventListener(MouseEvent.CLICK, clickHandler);
       removeChild(bar);
       bar = null;
}

在清除foo的引用之前执行destroy()方法:

Actionscript代码
北京联动北方科技有限公司

  1. foo.destroy();   
  2. removeChild(foo);   
  3. foo = null;  


foo.destroy();
removeChild(foo);
foo = null;

经过我们如此处理,两个对象的引用数全都为0。GC只需第一步处理就完成任务,我们节约了更多的运算开销!

从上例可以看出在一般情况下,监听子对象的事件不会影响GC。不管是弱引用方式的监听,还是显式移除监听,都只是帮助GC更高效运行的手段,而不会影响GC的结果。但是如果事件监听造成的是对象以外的引用关系,情况就不同了,并且很有可能造成回收失败。一个常见的错误例子是监听Stage对象的 RESIZE事件,如果没有显式移除这个监听或者是没有采用弱引用方式,那么这个对象就不会被GC回收的。所以我建议大家还是要尽可能的显式移除监听,切断引用关系。

我们现在已经用对GC最友好的方式做好了清理准备,但是对象还没有从内存中删除。在等待GC执行的这段时间,对象内的代码还在继续执行。比如加在对象上的ENTER_FRAME事件监听处理还在继续执行,对象内的MovieClip或Sound都还在继续播放。我们一定要避免这种情况的发生,所以在切断引用之前,我们还要在destroy()方法中做些清理工作。我们要做的工作包括:

  • clearInterval(),clearTimeout()
  • timer.stop()
  • loader.unload()/loader.unloadAndStop()
  • movieclip.stop() 如果有子MC的,也要停止播放
  • bitmapData.dispose()
  • 关闭LocalConnection,NetConnection,NetStream
  • 停止音频和视频的播放
  • 删除Camera和Microphone对象的引用
  • 调用子对象的destroy()方法,如果有的话

其实这些都是在开发中管理资源的基本常识,归结为一句话就是:谁创建了对象,谁就要负责清理该对象。

下面就以一些我在实际项目中开发的destroy()方法为例,看代码说话:

Actionscript代码
北京联动北方科技有限公司

  1. public function destroy():void 
  2.        // List是一个继承自Sprite的自定义子类 
  3.        
  4.        // 移除list的事件监听 
  5.        list.removeEventListener.remove(MouseEvent.CLICK, clickHandler); 
  6.        
  7.        // 我们创建了list实例,也要负责清理 
  8.        // 调用list对象自己的destroy()方法 
  9.        list.destroy(); 
  10.        
  11.        // 将list从显示列表移除 
  12.        // 这一步并非必须的步骤,原因是list的父对象会被移除,这样list和stage就没有联系了。 
  13.        // 但是在我的代码中,list还有可能会被添加到外部的容器中(比如stage),那么这一步就是必须的了。 
  14.        list.parent.removeChild(list); 
  15.        list = null; 
  16.        
  17.        // -------------------- 
  18.        // loader是一个来自tweenmax类库中的ImageLoader实例 
  19.        
  20.        // 如果loader已经创建 
  21.        if(loader) 
  22.        { 
  23.              // 调用loader的dispose()方法,优秀的第三方类库都应该有良好的资源管理机制。 
  24.              // 参数true表示把加载的内容从显示列表上移除,帮我们节约了代码。 
  25.              loader.dispose(true); 
  26.              loader = null; 
  27.        } 
  28.        
  29.        // -------------------- 
  30.        // lightbox实例变量保存了一个外部引用 
  31.        // 根据谁创建谁清理的原则,我们在这里不需要负责该对象的清理,只要删除引用就可以了。 
  32.        lightbox = null; 


public function destroy():void
{
       // List是一个继承自Sprite的自定义子类

       // 移除list的事件监听
       list.removeEventListener.remove(MouseEvent.CLICK, clickHandler);

       // 我们创建了list实例,也要负责清理
       // 调用list对象自己的destroy()方法
       list.destroy();

       // 将list从显示列表移除
       // 这一步并非必须的步骤,原因是list的父对象会被移除,这样list和stage就没有联系了。
       // 但是在我的代码中,list还有可能会被添加到外部的容器中(比如stage),那么这一步就是必须的了。
       list.parent.removeChild(list);
       list = null;

       // --------------------
       // loader是一个来自tweenmax类库中的ImageLoader实例

       // 如果loader已经创建
       if(loader)
       {
             // 调用loader的dispose()方法,优秀的第三方类库都应该有良好的资源管理机制。
             // 参数true表示把加载的内容从显示列表上移除,帮我们节约了代码。
             loader.dispose(true);
             loader = null;
       }

       // --------------------
       // lightbox实例变量保存了一个外部引用
       // 根据谁创建谁清理的原则,我们在这里不需要负责该对象的清理,只要删除引用就可以了。
       lightbox = null;
}

另一个示例的destroy()方法演示了对数组中对象的处理方法:

Actionscript代码
北京联动北方科技有限公司

  1. public function destroy():void 
  2.        // cells是一个数组,包含了一组子对象 
  3.        var i : int, n : int; 
  4.        n = cells.length; 
  5.        for(i = 0; i < n; i++) 
  6.        { 
  7.              // 执行每一个子对象的destroy()方法 
  8.              cells[i].destroy(); 
  9.              // 删除子对象的引用 
  10.              cells[i] = null; 
  11.        } 
  12.        
  13.        // 删除数组自身的引用 
  14.        cells = null; 
  15.        
  16.        // 如果不需要得到每一个子对象的引用,我们也可以简单的用以下代码来清理数组: 
  17.        //cells.length = 0; 
  18.        //cells = null; 


public function destroy():void
{
       // cells是一个数组,包含了一组子对象
       var i : int, n : int;
       n = cells.length;
       for(i = 0; i < n; i++)
       {
             // 执行每一个子对象的destroy()方法
             cells[i].destroy();
             // 删除子对象的引用
             cells[i] = null;
       }

       // 删除数组自身的引用
       cells = null;

       // 如果不需要得到每一个子对象的引用,我们也可以简单的用以下代码来清理数组:
       //cells.length = 0;
       //cells = null;
}

在结构更复杂的项目里,我们还可以抽象出一个IDestroyable接口,让需要执行清理的自定义对象实现这个接口。这样我们的清理代码可以写为:

Actionscript代码
该贴由koei转至本版2012-2-15 19:28:36



赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论