2008/01/02 | 关于Flash Player垃圾回收的文章
类别(Flash) | 评论(1) | 阅读(640) | 发表于 23:15

原文

Understanding garbage collection in Flash Player 9

翻译

目前我暂时在研究ActionScript 3.0,它的能力让我很激动。它的原生执行速度带来诸多可能(此句原文The raw execution speed by itself provides so many possibilities. raw本意未加工,原始的,这里的意思是指引入AVM2之后,ActionScript 3.0在执行速度上有了很大提高,所以使支持更复杂的组件成为可能,译者注)。它引入了E4X、sockets、byte 数组对象、新的显示列表模型、正则表达式、正式化的事件和错误模型以及其它特性,它是一个令人炫目的大杂烩。

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

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

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

关于垃圾收集器

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

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

ActionScript 代码
 
  1. // create a new object, and put a reference to it in a:  
  2. var a:Object = {foo:"bar"}  
  3. // copy the reference to the object into b:  
  4. var b:Object = a;  
  5. // delete the reference to the object in a:  
  6. delete(a);  
  7. // check to see that the object is still referenced by b:  
  8. trace(b.foo); // traces "bar", so the object still exists.  

如果我改变上述示例代码将b也删除,它会使我创建的对象不再有活动引用并等待对垃圾收集器回收。ActionScript 3.0 垃圾回收器使用两种方法定位无引用的对象 : 引用计数法和标识清除法。

引用计数法

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

这是一个例子:

ActionScript 代码
 
  1. var a:Object = {foo:"bar"}  
  2. // the object now has a reference count of 1 (a)  
  3. var b:Object = a;  
  4. // now it has a reference count of 2 (a & b)  
  5. delete(a);  
  6. // back to 1 (b)  
  7. delete(b);  
  8. // the reference count down is now 0  
  9. // the object can now be deallocated by the garbage collector  

引 用计数法简单,它不会非CPU带来巨大的负担;多数情况下它工作正常。不幸地是,采用引用计数法的垃圾回收器在遇到循环引用时效率不高。循环引用是指对象交叉引用(直接、或通过其他对象间接实现)的情况。即使应用程序不再引用该对象,它的引用计数器仍然大于0,因此垃圾收集器永远无法收集它们。下面的代码 演示循环引用是怎么回事:

ActionScript 代码
 
  1. var a:Object = {}  
  2. // create a second object, and reference the first object:  
  3. var b:Object = {foo:a};  
  4. // make the first object reference the second as well:  
  5. a.foo = b;  
  6. // delete both active application references:  
  7. delete(a);  
  8. 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 标记过程中经过,绿色对象被标记过,白色对象将被回收。

fig01

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

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

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

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

记住,在Flash Player中你无权控制何时运行垃圾收集器去回收对象。作为开发者,你需要尽可能把你的游戏或应用程序中无用的对象应用清除。管理无用对象的策略将会在我的另一篇文章重点介绍:Flash Player 9的资源管理策略。

下图模拟垃圾回收器中总内存(包含活动对象和非活动对象的和,译者注)的锯齿状部分(点击图2或下方链接)。锯齿的产生是由于垃圾回收器执行回收(原文执行 清除,疑为执行回收)。点击该图并注意,按下空格键来中止或重启,并在运行时按住up/down键头以控制内存使用趋势。

http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html#

Figure 2. Garbage collection simulation

Garbage collection simulation

正如下面演示的一样(单击图3或下方链接),拖动对象(圆角矩形)到桌面并建立它们之间的引用(使用连线,译者注)。点击run reference或mark and sweep按钮以查看哪些对象会被回收。对象上有标识到该对象的引用个数。

http://www.adobe.com/devnet/flashplayer/articles/garbage_collection.html#

Figure 3. Garbage collection simulation: mark and sweep

Garbage collection simulation: mark and sweep

从这里通往何方?

理解垃圾回收机制是编写优化代码的重要前提。优化的代码保证你的Flash项目在客户机器上运行时不会带来大的冲击。阅读更多相关文章,Flash Player 9中的资源管理策略,或访问Flash开发者中心以及Flash Player开发者中心。

也可以查看我的blog gskinner.com 阅读关于弱引用的文章并下载我写的一个辅助类。

关于作者

Grant Skinner是gskinner.com公司CEO及架构师,该公司是一个Flash开发和咨询公司。他与新媒体机构以及有进取心的客户合作开发前沿应用、游戏和多媒体程序。他擅于将支持接口设计的代码、可用性、市场和业务逻辑融汇,由此带给他国际上赞誉并获得包括Best Canadian Developer at FITC 2005在内的多个业界大奖。Grant在gskinner.com/blog/写blog并在incomplet.org展示他的验证性的创作。
  • E210cae1-bebd-4435-abb9-18c8b1689ddd-thumb
  • 描述:
  • 大小: 15.2 KB
  • 查看次数: 21
  • D9b06426-55b7-4019-a594-fdb84e4de9c6-thumb
  • 描述:
  • 大小: 8.6 KB
  • 查看次数: 25

 

 

第二篇

转自:http://www.kingda.org/archives/kingda/2007/10/as_hack.html

----------------------------------------------------------------------------------------
首先,要感谢校友学长“费gg”的帮助,提供了核心的hack技术的方法。
然后鉴于几天前一网友关于画上万个sprite之后内存增大问题,在此一并作一下讨论和解决。
-----------------------------------------------------------------------------------------
概述:AVM2理论上是自带垃圾回收机的,但是具体何时进行垃圾回收,理论上非人为所能控制,而我们若要强制启用垃圾回收机,则需要用一些非正规手段,比如如下会提到的手段。

关键字:垃圾回收

核心hack技术
批注:所谓强制执行垃圾回收机,是指通过故意使swf在运行时出错,然后throw出错误,而同时通过catch error来继续运行swf文件。而垃圾回收机则会在swf抛出错误的时候,被强制执行一次,以清除内存中无效的数据占用,减少资源的消耗。
我当初也是这么理解的,不过从“费gg”处所学到的,包括自己所测试的,并不是所有的error throw都能触发垃圾回收机,而也只局限于某些特定的error,而上文的例子中的这一error恰为其中的一种error类型。

 

try{
        new LocalConnection().connect("MoonSpirit");
        new LocalConnection().connect("MoonSpirit");
}catch(error : Error){

}

运行上述代码,可强制执行一次垃圾回收机。

具体测试:

分别用initNoBitmapDataView()和initBitmapDataView()进行10k个举行元素创建


测试结果:
1.1不使用BitmapData 内存占用 40M

2.1使用BitmapData 依旧内存占用 40M!
原因分析: 虽然_sqrList被设为null,但是原先被_sqrList所引用的1w个sprite数据依旧在内存中存在,虽然AMV2自带垃圾回收机,但是何时进行垃圾回收是不确定的。而要强制执行垃圾回收机,则要用上文提到的hack手段
2.2使用BitmapData + 垃圾回收hack 内存占用 12M

 

package {
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.Sprite;
        import flash.net.LocalConnection;

        public class MoonSpirit extends Sprite {
                private const SQR_AMOUNT                        : int = 10000;    //方块数量      
                private var _container_sp                : Sprite;          //容器sprite
                private var _sqrList                                : Array;           //所有方块的引用
              
                public function MoonSpirit() {
                        init( );                      
                }
              
                private function init( ) : void{
                        _container_sp = new Sprite( );
                        addChild(_container_sp);
                        //initNoBitmapDataView( );
                        initBitmapDataView( );
                }
              
                //初始化 通过通常手段 显示
                private function initNoBitmapDataView( ) : void {
                        layoutTenThousandSqr( );
                }
              
                //初始化 通过BitmapData快照 显示
                private function initBitmapDataView( ) : void {
                        layoutTenThousandSqr( );
                        var myBitmapDataObject : BitmapData = new BitmapData(150, 150, false, 0xFF0000);
                        var myImage:Bitmap = new Bitmap(myBitmapDataObject);
                        addChild(myImage);
                        unLayoutTenThousandSqr( );
                        _sqrList = null;
                        doClearance( );
                }
              
                private function layoutTenThousandSqr( ) : void {
                        _sqrList = new Array( );
                        for(var i : int = 0; i < SQR_AMOUNT; i++){
                                _sqrList.push(new Sprite());
                                _sqrList[i].graphics.beginFill(0xff0000);
                                _sqrList[i].graphics.drawRect(0,0,100,100);
                                _sqrList[i].graphics.endFill();
                                _container_sp.addChild(_sqrList[i]);
                        }
                }
              
                //不显示
                private function unLayoutTenThousandSqr( ) : void {
                        for(var i : int = 0; i < SQR_AMOUNT; i++){
                                _container_sp.removeChild(_sqrList[i]);
                                delete _sqrList[i];
                        }
                }
              
                //精髓,垃圾回收机强制调用
                private function doClearance( ) : void {
                        trace("clear");
                        try{
                                new LocalConnection().connect("foo");
                                new LocalConnection().connect("foo");
                        }catch(error : Error){
                              
                        }                      
                }
        }
}

1

评论Comments

日志分类
首页[186]
Flash[84]
FMS[41]
AIR[2]
ASP[11]
作品区[12]
其他[36]