日期:2013-12-02  浏览次数:20722 次

http://as3blog.com/as3/as3tip-take-care-of-resource

摒弃了attachMovie之后的AS3,采用了类似DOM的操作方式。addChild、removeChild、getChildAt等方法开始成为AS3中显示(在屏幕上渲染)、操作图形的次要方法。由于AS1、AS2完全是依赖于attchMovie的思想,因此对于传统Flash开发人员来说,转变到新的addChild的确需求下一番功夫。

由于新的“DisplayObject”在内存的使用上非常“敏感”。往往由于不良的编程习惯会形成不必要的内存走漏,因此,我们不得不比AS1、AS2时代愈加深入到内存管理了。我想,每一个Flash开发人员,包括我本人,都应该花一番功夫细心体会“内存管理”这几个字的含义。毕竟我们学习AS3是为了开发比AS1、2时代愈加先进、高效而且内存占用小的使用程序,如果还是开发一些简单的使用,也就得到我们每一团体使用AS3的意义了。

我觉得,由于GC是Flash使用程序内存管理的核心,我应该先从AS3中的Garbage Collector(简称GC)开始说起。AS3的GC功用比AS1、2中的要强大的多。然而,强大的同时,也带来了一定程度的复杂性。但是也不至于非常复杂,我觉得比C++等传统言语要容易掌握得多。

在研讨内存如何回收之前,先说一下变量的创建:
在Flash中,我们每建立一个非原生变量时(Boolean, String, Number, uint, int这些是原生变量),这个变量名只是一个reference(指向,有时候也成为“援用”)而已,而并非这个变量本身。例如:
var a:int = 5; //a就是5
var b:int = a; //b是a,也就是5
a = 4;
trace(b); // 是5,而不是4!
//改变b的值,a不发生变化。反之亦然
var c:Object = {name:”aw”, blog:”www.awflasher.com/blog”}; //c只是指向一个内部的Object,为了描述方便,称其为“O”
var d:Object = c; //d指向c,指向了同一个Object“O”
c.name = “bw”
trace(d.name); //不是aw,而是bw了
//改变c也好,改变d也罢,其实是改变了那个“O”,因此改变c的时候,d的值也就变了。因此d.name曾经被改变为了“bw”。

首先,明确一点,GC会按照一定的时间周期进行内存清理(memory sweep)。因此不要由于delete掉一个object后检查System.totalMemory内存就没有反应而怀疑GC能否正常。

那么GC为什么要按照一定的时间周期进行清理呢。这还要得从GC的具体任务原理说起。

GC的两个回收体系:
1、“Reference Counting” - 援用计数器
这集体系是自从AS1时代就有的体系,它的任务原理非常简单:系统计算每一个对象被指向,或者说援用的次数。比如
var a:Object = {name:”aw”, blog:”www.awflasher.com/blog”};
这时候,这个Object(我们仍然称为“O”),有一次援用,它的援用计数器为1(来自a)。
我们再建立一个对象b,并指向到a:
var b:Object = a;
这时候“O”援用计数器变为了2(来自a、b)
我们删除一个,比如先删除b:
delete b;
这时候援用计数器为(2-1=1)1,GC不操作
再删除另外一个a:
delete a;
“O”援用计数器变为(1-1=0)0,GC出面干掉这个对象。
这套体系很轻便,CPU压力较小。但是它也有缺陷。当我们的对象内部互相援用的时候,麻烦就来了。例如:
var a:Object = {name:”aw”, description:”unknown”};
// 建立一个对象a,仍然假设内部对象为“O”,这时O的援用次数为1
var b:Object = {nameObj:a, url:”awflasher.com”};
// b援用了a,同时创建了新的内部对象“P”。这时O的援用次数为2,P为1
a.myDescription = b;
// a的myDescription属性指向到了b。这样,P的援用次数也为2了。
// 是不是有点头晕?静下来,画个图慢慢看看:)
delete a;
delete b;
两次delete操作后,O、P的援用次数都是1,它们将继续占用你的内存。“Reference Counting” 体系无能为力了。

2、“Mark Sweeping” - 标记清除法则
GC的第一种机制“Reference Counting”,在FlashPlayer8之前是GC独一的机制。FlashPlayer6和7由于引入了复杂的OOP开发模式,尤其是7引入了类似Java、C++等强大OOP言语的语法。利用Flash设计的复杂项目越来越多。由于Flash开发人员大多不了解GC,而Java、C++的开发人员又曾经习惯了强大的GC(无论是自动的还是手动的)。因此FlashPlayer6、7的内存问题开始浮现出来。
Okay,Flash Player8引入了新的“Mark Sweeping”机制。我想这也是当年Marcomedia(Adobe)基于退出Player8的缘由吧!(还记得当年Flash8的引见视频么,效率提高是一个反动性的改进)
下面就来讲述“Mark Sweeping” - 标记清除法则的任务原理。
FlashPlayer会从root开始,遍历系统的每一个变量,并对有指向的对象之间,记录一次联系。在遍历结束之后,凡是与root不相联系的对象,被FlashPlayer无情地干掉:)
Okay,回到刚才的例子,当我们delete a,并delete b之后,root与O、P就划清了界线。这时候,GC就可以进行一次肃清了。
然而,由于这种“Mark Sweeping”要遍历所有的对象,因此非常耗费资源。这也就回到了当初的问题:“GC为什么要按照一定的时间周期进行清理“ - 由于不能给CPU形成太大的负担。
我团体猜测,GC的内部清除策略应该是在某一次事件(例如delete)发生后,在CPU比较空闲、RAM分配绝对合理的情况下执行的。

Okay,不要以为有了“Mark Sweeping”就万事大吉了。由于GC不会即时进行,因此你的对象会在一段时间内“阴魂不散”!对于一个追求完满的开发人员来说,这意味着它们内部的某些机制会在被删除之后继续任务:AS语句会继续执行、声音会继续播放、事件会继续触发!

KirupaForum有网友说,“All you got to do is pray the garbage collector doesn’t break down.”,确实,如果一个使用程序要运转上一个多小时,那么慢慢流逝的内存会让你的用户对你的产品失望(例如游戏)。因此我们需求有一个良好的资源管理策略。