[翻译]理解AS3的垃圾回收机制(上)

原文链接:http://active.tutsplus.com/tutorials/workflow/quick-tip-understanding-garbage-collection-in-as3/

难度: 中等

项目: Flash Develop, Flex Compiler

预计完成时间: 45分钟

你是否有用过Flash应用?是否有感觉到延迟(lag)?到现在还不知道为什么那么炫的Flash应用在你的电脑上如此之慢?如果你想知道可能的成因是什么,那么本文正好适合你。

结果预览

先看看我们实验的最终结果:

第一步 参考应用的快速复习

进入实际项目之前,你需要先了解一点as3中的实例化和引用的工作原理。如果你已知道,还是建议你看一下,这样的话可以给你的脑海里留下新鲜的印象,然后你可以很顺畅的看完本文其他部分。

as3中对象的创建和应用和大部分认为的不一样。一些对象的只在代码要求创建的时候创建和实例化。一般,通过关键字new触发。但是在使用literal sytax(文字语法)或函数定义参数的时候,他也会触发。

范例如下:

// 使用关键字 "new" 实例化
new Object();
new Array();
new int();
new String();
new Boolean();
new Date();
 
// 使用文字语法实例化
{};
[];
5
"Hello world!"
true
 
//通过函数参数实例化
private function tutExample(parameter1:int, parameter2:Boolean):

  对象创建后,他们在被引用之间都是处于游离(alone,孤单)状态。如何引用一个对象呢?你只需要创建一个变量,然后将对象的值传给这个变量即可。因此,他就可以知道他当前所持有的值了。尽管如此,当你将一个变量的值赋给另一个变量,你并没有创建一个新的对象(大部分人不知道)。你只是创建了这个对象的另一个访问途径(link,连接)。下图比较简单明了:

这张图假设Variable 1 和Variable 2都可以持有这个表情(例如,他们可以持有相同的类型的值)。在左边,只有一个Variable 1。这样,我们可以创建Variable 2,将他设为Variable 1同样的值,我们并没有在Variable 1 和Variable 2之间创建连接。(如图片的左上部分) 我们只是创建了Smiley与Variable 2之间的链接(图片的右下部分)。

有了以上知识,我们可以开始进行Garbage Collector之旅了。

第二步 每个城市需要一个垃圾回收站

好明显,任何应用都需要一定的空间来运行。就像他们需要变量来持有和使用它们一样。不清楚的地方是应用如何管理那些不在需要的对象,需要回收不?需要删除不?还是让他们待在内存里面直到应用关闭?以上三者,皆有可能,我们重点将的是第二和第三个。

想像一下,当启动一个应用的时候,创建了大量实例(Object,对象,物件)。但是当启动完成后,大部分的实例并没有用处。如果把他们就这么放在那里,会发生什么呢?他们当然会在里面占用很大一个空间。如此一来就导致了人们所看到的延迟。你会很明显的感觉到应用变迟钝。这样的用户体验肯定是没人喜欢的,所以我们要避免这种事情发生。如何编码可以让我们的应用更搞笑的运行呢?答案就是垃圾回收器。

垃圾回收是内存管理的一种形式。他旨在排除任何不适用但是又占用系统内存的对象,这样,应用可以使用最小的内存来运行,如下图:

当启动应用的时候,他会想系统申请一部分的内存,应用运行之后,他将在这一片内存里填充你的信息,你创建的对象都会放到这里。如此一来,当内存使用接近初始化申请的内存的时候,垃圾回收器将开始运作,寻找内存里任何设为空但是不使用的对象。有些时候,由于寻找对象的开销过大,垃圾回收将会导致一点延迟。

在图片里,你可以看到内存峰值(绿色圈圈里头)和突然之间的跌落,这就是内存回收所致。这里当应用内存使用达到请求的内存(红色线)的时候,移除所有不需要的对象。

第三步  开始SWF文件

现在,我们知道了GC可以为我们做些什么,我们可以用它来编程了。

首先,我们必须用一个直观的试图展示GC是如何工作的。

在编写代码的时候,对象在无法触及的时候可以进行垃圾回收。当一个对象不可以访问的时候,代码知道它不再使用,然后可以回收他。

as3通过垃圾回收根(Garbage Collection Roots,太长了,以下简称GCR)检测可达性。当一个对象不可以通过GCR访问的时候,他可以进行回收。以下是GCR的条件列表:

1 包级别和静态变量

2 本地变量和局部变量(方法体内的变量)

3 应用主类实例里的实例变量或应用类实例的显示列表

为了探明GC如何操作对象,我们需要编写代码和检验范例文件里头发生了什么。我将使用Flash Develop as3工程和Flex编译器结合。当然你也可以使用被的IDE。我制作了一个简单的文件,里面有一个按钮和文本结构。由于这个不是本文的主要目的,所以只需要简单的介绍一下工作原理:点击按钮的时候触发一个方法。点击你想显示的文本可以让他显示到屏幕上。还有一些其他的文本作为按钮的标签。

我们范例的目的是创建对象,删除对象,检查删除他们之后发生的事情。我们需要一个方法探知对象的生死。所以我们给每个对象添加ENTER_FRAME事件监听器。这样一来,只要他们或者的话他们会一直显示一些文本。

我给对象创建了一个开心的表情图片。图片来源于Micheal James Williams牛逼的Avoider游戏手册。每个对象头顶将有一个数字。这样我们可以轻松的识别他们。同时,我将第一个对象命名为TheObject1,第二个对象命名为TheObject2,这样很容易区别。编码如下:

private var _theObject1:TheObject1;
 
private function newObjectSimple1(e:MouseEvent):void
{
    // 如果_theObject1已存在,略过
    if (_theObject1)
        return;
     
    // 创建新对象,设定到指定位置,添加到显示列表
    _theObject1 = new TheObject1();
    _theObject1.x = 320;
    _theObject1.y = 280;
    _theObject1.addEventListener(Event.ENTER_FRAME, changeTextField1);
     
    addChild(_theObject1);

  第二个对象看起来差不多:

private var _theObject2:TheObject2;
 
private function newObjectSimple2(e:MouseEvent):void
{
    // 如果对象已存在,略过
    if (_theObject2)
        return;
     
    //创建新对象,设置指定位置,添加到显示列表
    _theObject2 = new TheObject2();
    _theObject2.x = 400;
    _theObject2.y = 280;
    _theObject2.addEventListener(Event.ENTER_FRAME, changeTextField2);
     
    addChild(_theObject2);
}

  在代码中,newObjectSimple1()和newObjectSimple2()将会点击对应的按钮的时候触发。这些方法只是简单的创建一个对象并将其显示到屏幕上。这样我们可以直观的看到它被创建了。另外,他在每个对象上创建了一个EnterFrame监听器。监听器在他们或者的每秒显示一些信息。方法如下:

private function changeTextField1(e:Event):void
{
    // 范例的帧频是 30FPS, 所以我们每帧加1/30到指定的数字
    _objectCount1 += 0.034;
     
    // 检查_objectCount1 是否超过1秒
    if(int(_objectCount1) > _secondCount1)
    {
        // 在屏幕上显示一个文本
        displayText("Object 1 is alive... " + int(_objectCount1));
         
        _secondCount1 = int(_objectCount1);
    }
}

  

private function changeTextField2(e:Event):void
{
    // Our example is running at 30FPS, so let's add 1/30 on every frame in the count.
    _objectCount2 += 0.034;
     
    // Checks to see if _objectCount2 has passed one more second
    if(int(_objectCount2) > _secondCount2)
    {
        // Displays a text in the screen
        displayText("Object 2 is alive... " + int(_objectCount2));
         
        _secondCount2 = int(_objectCount2);
    }
}

  这些方法在对象活着的时候显示他们的信息,以下是当前得出的swf文件:

第四步 删除对象

现在完成了对象的创建,我们现在考虑一下:你是否想过当你真的删除(删除所有引用)掉一个对象之后,会发生什么?他是不是被回收了?这是接下来我们要测试的。我们将创建两个删除按钮,每个对象一个。

private function deleteObject1(e:MouseEvent):void
{
    // 把他从显示列表移除之前检查它是否存在于显示列表
    if (_theObject1 && contains(_theObject1))
        removeChild(_theObject1);
     
    // 移除对象的所有引用(这是他唯一的引用)
    _theObject1 = null;
     
    // 在屏幕上显示文本
    displayText("Deleted object 1 successfully!");
}

  

private function deleteObject2(e:MouseEvent):void
{
    // Check if _theObject2 really exists before removing it from the display list
    if (_theObject1 && contains(_theObject2))
        removeChild(_theObject2);
     
    // Removing all the references to the object (this is the only reference)
    _theObject2 = null;
     
    // Displays a text in the screen
    displayText("Deleted object 2 successfully!");
}

  来看一下现在的swf,你觉得会发生什么?

如你所见,当你点击Create Object1,然后点击Delete Object1时候,基本上没事发生。我们只知道代码执行了。因为屏幕上有字。但是为什么对象没有被删除呢?对象还在因为他根本没有被移除。当我们清除了它的所有引用,告诉代码他可以进行垃圾回收了。但是垃圾回收器根本没有运行。记住,垃圾回收器只在应用的使用内存接近申请内存的时候才会运行。这不科学!但是我们要怎么验证呢?

我当然不会写一片代码去创建一堆对象填满应用空间以达到垃圾回收的条件。我们将使用一个Adobe公司目前不支持的方法,强制执行GC。参考Grant Skinner的文章。这样,我们可以使用这个简单的方法触发,然后可以看到当他运行的时候发生的事情。(同时,垃圾回收以下将简称GC)代码如下:

private function forceGC(e:MouseEvent):void
{
    try
    {
        new LocalConnection().connect('foo');
        new LocalConnection().connect('foo');
    }
    catch (e:*) { }
     
    // Displays a text in the screen
    displayText("----- Garbage collection triggered -----");
}

  这个简单的方法创建了两个LocalConnection实例,用来强制垃圾回收。这样,我们需要的时候就调用它。在正规的应用里面,不建议用这个方法。如果只是用来测试的话,没有什么问题。如果用于正式项目,会产生一些不可预知的负面效果。我建议让GC顺其自然的进行。不要强制,强扭的瓜不甜。我们只要有效的编码,将不会有内存问题发生(在第六步中将涉及)。现在,回到我们只做的SWF,在创建和删除随想后,点击“Garbage Collection”。

测了没?他确实管用!你可以看到。在删除对象后出发GC。他将对象移除了,注意,当你没有删除对象而调用GC,没事儿发生。因为他还在代码里面有引用。现在我们给对象两个引用然后移除其中的一个。

原文地址:https://www.cnblogs.com/adoontheway/p/2837884.html