原文地址:http://kevincao.com/2011/08/actionscript-garbage-collection-1/

谈谈ActionScript垃圾回收(上)

在《给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过程更高效的执行,最好是能在第一步引用计数中就把需要回收的对象都标记出来。具体的做法就是把所有不需要的对象引用全部清空,包括:

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

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

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

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

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

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

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

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

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

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

  1. foo.destroy();
  2. removeChild(foo);
  3. 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()方法为例,看代码说话:

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

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

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

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

  1. var n:int = this.numChildren;
  2. for(var i:int = n-1; i >= 0; i--)
  3. {
  4. if(this.getChildAt(i) is IDestroyable)
  5. {
  6. IDestroyable(this.getChildAt(i)).destroy();
  7. this.removeChildAt(i);
  8. }
  9. }

总结:GC好比是ActionScript城市的环卫工人,我们的每个类都是从事劳动生产的市民。优秀的市民会把生产垃圾分类安放到回收点,而不文明的市民则把垃圾丢得到处都是。你说那种做法让城市的清扫工作变得更加高效?所以请大家谨记“谁创建谁清理”的原则,做一位ActionScript好市民。

本文回顾了GC的一些基本知识和最佳实践。在下一篇中我将结合一些具体问题,为大家把脉GC的疑难杂症。敬请期待。

继续阅读:

谈谈ActionScript垃圾回收(下)

参考资料:

[转] gc tips(2)的更多相关文章

  1. [转] gc tips(3)

    原文地址:http://kevincao.com/2011/08/actionscript-garbage-collection-2/ 谈谈ActionScript垃圾回收(下) 前文我们介绍了GC的 ...

  2. [转] gc tips(1)

    所有应用软件都需要管理内存,一个应用软件的内存管理系统包括了如下准则:什么时候派发内存,要派发多少内存,什么时候把东西放到回收站,以及什么时候清空回收站.MMgc是Flash Player几乎所有内存 ...

  3. Best Practices for Performance_1、2 memory、Tips 性能和小的优化点、 onTrimMemory

    http://developer.android.com/training/articles/memory.htmlhttp://developer.android.com/tools/debuggi ...

  4. Java Hotspot G1 GC的一些关键技术

    G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推出,相 ...

  5. Understanding CMS GC Logs--转载

    原文地址:https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs Understanding CMS GC Logs By Po ...

  6. How-to go parallel in R – basics + tips(转)

    Today is a good day to start parallelizing your code. I’ve been using the parallel package since its ...

  7. Android应用程序性能优化Tips

    对于我们设计的应用需要做到以下特征:build an app that's smooth, responsive(反应敏捷), and uses as little battery as possib ...

  8. golang ----gc问题

    go程序内存占用大的问题 这个问题在我们对后台服务进行压力测试时发现,我们模拟大量的用户请求访问后台服务,这时各服务模块能观察到明显的内存占用上升.但是当停止压测时,内存占用并未发生明显的下降.花了很 ...

  9. golang 垃圾回收 gc

    http://ruizeng.net/golang-gc-internals/ 摘要 在实际使用go语言的过程中,碰到了一些看似奇怪的内存占用现象,于是决定对go语言的垃圾回收模型进行一些研究.本文对 ...

随机推荐

  1. UVA 11076 Add Again 计算对答案的贡献+组合数学

    A pair of numbers has a unique LCM but a single number can be the LCM of more than one possiblepairs ...

  2. lintcode:等价二叉树

    等价二叉树 检查两棵二叉树是否等价.等价的意思是说,首先两棵二叉树必须拥有相同的结构,并且每个对应位置上的节点上的数都相等. 样例 1 1 / \ / \ 2 2 and 2 2 / / 4 4 就是 ...

  3. Junit单元测试学习笔记一

    我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能很强大,但我们在程序中只用到该函数的一小部分功能,并且经过调试可以确定,这一小部分功能是正确的.但是,我们同时应该确保每一个函数 ...

  4. java中的基本数据类型存放位置

    基本数据类型是放在栈中还是放在堆中,这取决于基本类型声明的位置. 一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中, ...

  5. iOS开发--项目内存优化

    在用非ARC模式编写iOS程序的时候,造成程序内存泄露在所难免,后期我们一般会进行内存优化.自己比较常用的内存优化方法有两种 1.Analyze,静态分析内存泄露的方法.很简单,在Xcode菜单栏中点 ...

  6. 关于SecureCRT链接虚拟机和开发板的问题

    SecureCRT链接虚拟机后会出现汉字显示乱码问题,一下是解决方案. 点击options 再点击session options 会出现 选择: 主要改两个地方: normal  和 改完之后就可以顺 ...

  7. 300. Longest Increasing Subsequence

    题目: Given an unsorted array of integers, find the length of longest increasing subsequence. For exam ...

  8. C# 公关类(全)

    http://tool.sufeinet.com/CodePreview/CodeView.aspx?action=view&file=FTP/FTPClient.cs

  9. spring依赖注入单元测试:expected single matching bean but found 2

    异常信息:org.springframework.beans.factory.UnsatisfiedDependencyException: Caused by: org.springframewor ...

  10. Difference between Pragma and Cache-control headers?

    Pragma is the HTTP/1.0 implementation and cache-control is the HTTP/1.1 implementation of the same c ...