现在的位置: 首页 > 综合 > 正文

AS3垃圾回收机制

2013年04月11日 ⁄ 综合 ⁄ 共 5973字 ⁄ 字号 评论关闭

先了解下回收的原理,如果觉得麻烦,可以直接看最后的方法
摘自:http://www.duzengqiang.com/blog/article.asp?id=245

AS3垃圾收集器不再支持自动为你收集垃圾等假设。也就是说Flash开发者转到ActionScript 3.0之后需要对关于垃圾收集如何工作以及如何编程使其工作更加有效具备较深入的理解。没有这方面的知识,即使创建一个看起来很简单的游戏或应用程序也会出现SWF文件内存泄露、耗光所有系统资源(CPU/内存)导致系统挂起甚至机器重启。

要理解如何优化你的ActionScript 3.0代码,你首先要理解垃圾收集器如何在Flash Player 9中工作。Flash有两种方法来查找非活动对象并移除它们。本文解释这两种技术并描述它们如何影响你的代码。

本文结尾你会找到一个运行在Flash Player9中的垃圾收集器模拟程序,它生动演示了这里解释过的概念。

关于垃圾收集器

垃圾收集器是一个后台进程它负责回收程序中不再使用的对象占用的内存。非活动对象就是不再有任何其他活动对象引用它。为便于理解这个概念,有一点非常重要,就是要意识到除了非原生类型(Boolean, String, Number, uint, int除外),你总是通过一个句柄访问对象,而非对象本身。当你删除一个变量其实就是删除一个引用,而非对象本身。

以下代码很容易说明这一点:

            ActionScript 代码
            
            
            
                // create a new object, and put a reference to it in a:  
                var a:Object = {foo:"bar"}  
                // copy the reference to the object into b:  
                var b:Object = a;  
                // delete the reference to the object in a:  
                delete(a);  
                // check to see that the object is still referenced by b:  
                trace(b.foo); // traces "bar", so the object still exists.  
            
            
            如果我改变上述示例代码将b也删除,它会使我创建的对象不再有活动引用并等待对垃圾收集器回收。ActionScript 3.0 垃圾回收器使用两种方法定位无引用的对象 : 引用计数法和标识清除法。

引用计数法

引用计数法是一种用于跟踪活动对象的较为简单的方法,它从 ActionScript 1.0开始使用。当你创建一个指向某个对象的引用,该对象的引用计数器加1;当你删除该对象的一个引用,该计数器减1。当某对象的计数器变成0,该对象将被标记以便垃圾回收器回收。

这是一个例子:

            ActionScript 代码
            
            
            
                var a:Object = {foo:"bar"}  
                // the object now has a reference count of 1 (a)  
                var b:Object = a;  
                // now it has a reference count of 2 (a & b)  
                delete(a);  
                // back to 1 (b)  
                delete(b);  
                // the reference count down is now 0  
                // the object can now be deallocated by the garbage collector  
            
            
            引 用计数法简单,它不会非CPU带来巨大的负担;多数情况下它工作正常。不幸地是,采用引用计数法的垃圾回收器在遇到循环引用时效率不高。循环引用是指对象交叉引用(直接、或通过其他对象间接实现)的情况。即使应用程序不再引用该对象,它的引用计数器仍然大于0,因此垃圾收集器永远无法收集它们。下面的代码 演示循环引用是怎么回事:

            ActionScript 代码
            
            
            
                var a:Object = {}  
                // create a second object, and reference the first object:  
                var b:Object = {foo:a};  
                // make the first object reference the second as well:  
                a.foo = b;  
                // delete both active application references:  
                delete(a);  
                delete(b);  
            
            
            上 述代码中,所有应用程序中活动的引用都被删除。我没有任何办法在程序中再访问这两个对象了,但这两个对象的引用计数器都是1,因为它们相互引用。循环引用 还可以更加负责 (a 引用 c, c引用b, b引用a, 等等) 并且难于用代码处理。Flash Player 6 和 7的XML对象有很多循环引用问题: 每个 XML 节点被它的孩子和父亲引用,因此它们从不被回收。幸运的是Flash Player 8 增加了一个叫做标识-清除的新垃圾回收技术。

标识-清除法

ActionScript 3.0 (以及 Flash Player 8) 垃圾回收器采用第2种策略标识-清除法查找非活动对象。Flash Player从你的应用程序根对象开始(ActionScript 3.0中简称为root)直到程序中的每一个引用,都为引用的对象做标记。

接下来,Flash Player遍历所有标记过的对象。它将按照该特性递归整个对象树。并将从一个活动对象开始能到达的一切都标记。该过程结束后,Flash Player可以安全的假设:所有内存中没有被标记的对象不再有任何活动引用,因此可以被安全的删除。图1 演示了它如何工作:绿色引用(箭头)曾被 Flash Player 标记过程中经过,绿色对象被标记过,白色对象将被回收。

Figure 1. Flash Player采用标记清除方法标记不再有活动引用的对象

标记-清除法非常准确。但是,由于 Flash Player 遍历你的整个对象结构,该过程对CPU占用太多。Flash Player 9 通过调整迭代标识-清除缩减对CPU的占用。该过程跨越几个阶段不再是一次完成,变成偶尔运行。

延期(执行)垃圾回收器和不确定性

Flash Player 9垃圾回收器操作是延期的。这是一个要理解的非常重要的概念:当你的对象的所有引用删除后,它不会被立即删除。而是,它们将在未来一个不确定的时刻被 删除(从开发者的角度来看)。垃圾收集器采用一系列启发式技巧诸如查看RAM分配和内存栈空间大小以及其他方法来决定何时运行。作为开发者,你必须接受这样的事实:不可能知道非活动对象何时被回收。你还必须知道非活动对象将继续存在直到垃圾收集器回收它们。所以你的代码会继续运行(enterFrame 事件会继续)、声音会继续播放、装载还会发生、其它事件还会触发等等。

下面介绍回收的有效方法:

由这副图可以看出来

当你的类中包含的对象越多,嵌套越深,对象越复杂,系统回收发费时间就越长。而我们如果自行把这些对象与嵌套拆开来,使得每个复杂的对象变成一个一个的非常小的对象,那么回收所花的时间就会大大减少了。所以我们写完类,记得给类加上一个垃圾回收器,如:
package buildworld
{
        import flash.display.Sprite;
        import mypkg.MapBlock;
        public class Map extends Sprite
        {
                private var map:Array = new Array();
                private var mapName:String = "map1";
               
                public function Map(nw:int,nh:int):void
                {
                        this.createMap();
                }
                public function createMap():void
                {
                        for(var h = 0;h < this.nHeight;h++)
                        {
                                this.map[h] = new Array();
                                for(var l = 0;l < this.nWidth;l++)
                                {
                                                                                //MapBlock是个类,
                                        this.map[h][l] = new MapBlock();
                                       
                                }
                        }
                }
                //垃圾回收器
                public function gc():void
                {
                        for(var h = 0;h < this.nHeight;h++)
                        {
                                for(var l = 0;l < this.nWidth;l++)
                                {
                                       
                                        this.removeChild(this.map[h][l]);
                                        this.map[h][l].gc();//调用MapBlock中的垃圾回收器
                                        this.map[h][l] = null;
                                }
                        }
                        mapName = null;
                }
        }
}
那么最终进行垃圾回收就方便多了。

不知道大家还有更好的方法没?

===============================================================

  1. 我来解释下为什么LocalConnection可以实现垃圾回收.
  2. 其实LocalConnection与垃圾回收是没有直接关系的.
  3. 这个做法的原理在于垃圾回收的时间尽管不确定,但是,只要程序抛出错误,就会运行一次垃圾回收器.这里使用LocalConnection两次connect同一个连接,第二次将发生运行时错误(#1034,LocalConnection已经连接上).于是就报错了.垃圾回收器自动运行.
  4. 至于为什么网上流传的版本都是LocalConnection,我个人觉得,可能是因为这个LocalConnection在AS3的应用中相对比较少出现,而且跟其他代码相比,这个运行时错误不容易与其他代码发生冲突.

-----------------------------------------------------------------------

LocalConnection以及System.totalMemory这样的东西。
都是封装在底层的实现中。

如果是windows有相应的C++底层实现,官方没有公开这个资料自然也没有说明,不然就不叫Hack
但是我觉得这是一个不稳定的做法,因为针对不同的系统,相对的底层实现也不同。

-----------------------------------------------------------------------

我认为LZ的这个方法有待斟酌。
虽然降低了GC一次的运行时间,但拆开对象的嵌套结构仍然是要消耗的。所以,效果可能并不明显。

大量对象尽量不要循环引用,尽量让引用数起作用。
复杂的引用可以考虑用dictionary的弱引用特性。

比如下面是一个弱引用的类:

  1. package {
  2.         import flash.utils.Dictionary;       
  3.         public class WeakReference{
  4.                 private var dict:Dictionary;               
  5.                 public function WeakReference(obj:Object) {
  6.                         dict = new Dictionary(true);
  7.                         dict[obj] = null;
  8.                 }               
  9.                 public function get object():Object {
  10.                         for (var o:Object in dict)return o;
  11.                         return null;
  12.                 }
  13.         }
  14. }

复制代码

使用示例:

  1. //创建2个对象初始引用
  2. var obj1:Object = { name:"flash", version:"9.0" } ;                       
  3. var obj2:Object = { name:"silverlight", version:"1.0" } ;                       
  4. //再分别创建2个对象一个强引用和一个弱引用
  5. var strRef:Object = obj1;
  6. var weakRef:WeakReference = new WeakReference(obj2);
  7. //把2个对象的初始引用删除
  8. obj1 = obj2 = null;                       
  9. try {
  10.         new LocalConnection().connect('gc');
  11.         new LocalConnection().connect('gc');
  12. } catch (e:*) {}
  13. //GC后,弱引用的对象已经删除
  14. trace(strRef,weakRef.object);

复制代码

我觉得更重要的应该是程序的结构设计,结构设计的好坏才是对内存回收影响最大的。

http://bbs.9ria.com/thread-234-1-1.html

抱歉!评论已关闭.