ActionScript - 强制垃圾收集在ADL中不起作用?

时间:2010-11-16 23:49:45

标签: actionscript-3 garbage-collection air addeventlistener

在ADL中启动以下代码时,为什么方块继续旋转?

var square:Sprite = new Sprite();
square.graphics.beginFill(0xFF0000);
square.graphics.drawRect(-25, -25, 50, 50);
square.x = square.y = 100;
addChild(square);

addEventListener(Event.ENTER_FRAME, rotateSquare, false, 0, true);

function rotateSquare(evt:Event):void
    {
    square.rotation += 2;
    }

System.gc();

更新

以下显示对象具有弱引用的ENTER_FRAME事件侦听器。但是,打电话:

removeChild(testInstance);
testInstance = null;

不会停止ENTER_FRAME事件:

package
{
import flash.display.Sprite;
import flash.events.Event;

public class Test extends Sprite
    {       
    private var square:Sprite;

    public function Test()
        {
        addEventListener(Event.ADDED_TO_STAGE, init);
        }

    private function init(evt:Event):void
        {
        removeEventListener(Event.ADDED_TO_STAGE, init);

        square = new Sprite();
        square.graphics.beginFill(0xFF0000);
        square.graphics.drawRect(-25, -25, 50, 50);
        square.x = square.y = 100;
        addChild(square);

        addEventListener(Event.ENTER_FRAME, rotateSquare, false, 0, true);

//      //Current Solution - only works on display objects
//      addEventListener(Event.REMOVED_FROM_STAGE, removeHandler);
        }

    private function rotateSquare(evt:Event):void
        {
        trace("square is rotating");
        square.rotation += 2;
        }

//  private function removeHandler(evt:Event):void
//      {
//      removeEventListener(Event.REMOVED_FROM_STAGE, removeHandler);
//      removeEventListener(Event.ENTER_FRAME, rotateSquare);
//      }
    }
}

我添加了REMOVED_FROM_STAGE事件侦听器,但这只适用于显示对象。

这是ENTER_FRAME事件特有的问题吗?

2 个答案:

答案 0 :(得分:5)

关于您的更新,我认为您误解了GC的工作原理。基本想法很简单。

创建对象时,flash会在称为堆的存储中分配一些内存。返回对此对象的引用。此引用是您存储在变量中的内容。什么是参考?一种访问此对象的方法。把它想象成对象的链接。

var foo:Bar = new Bar();

现在,在某些语言中,在某些时候你必须释放为这个对象分配的内存,或者你有内存泄漏。

在GC环境中,这是自动完成的。当然,你需要一些规则。此规则因具体GC而异,但一般而言,您可以说GC确定如果对象不再可访问,则该对象是可收集的。这是有道理的,因为如果你无法到达某个对象,就无法使用它。你丢失了它的链接。所以,它被认为是垃圾,最终将被收集。

确定可达性的具体细节有所不同,但在闪存中它是参考计数和标记和扫描算法的混合。

(以下只是高级概述,细节可能不准确)

一种方法是引用计数:它既简单又快速,但并不适用于所有情况。基本上,每个对象都有一个引用计数。每次将此对象分配给变量(即存储对象的引用)时,引用计数都会递增。每次丢失此引用时(例如,将null清空),此计数将减少。如果计数达到0,则表示该对象无法访问,因此它是可收集的。

在某些情况下这种方法很好,但没有其他情况。特别是当有交叉引用时。

var foo1:Bar = new Bar();   //  let's call this object Bar_1
var foo2:Bar = new Bar();   //  let's call this one Bar_2
//  at this point, Bar_1 has reference count of 1 (foo1) and Bar_2 has a reference of 1 (foo2)
foo1.theOtherFoo = foo2;
//  now Bar_2 has a RC of 2: foo2 and foo1.theOtherFoo
foo2.theOtherFoo = foo1;
//  now Bar_1 has a RC of 2: foo1 and foo2.theOtherFoo
foo1 = null;
//  foo1 no longer references Bar_1, so its RC is decremented. 
foo2 = null;
//  foo2 no longer references Bar_2, so its RC is decremented. 
//  but still both Bar_1 and Bar_2 have a RC of 1. 

如您所见,Bar_1和Bar_2的RC均为1,但无法访问。这是引用计数不起作用的情况之一。因为所有意图和目的,两个对象都无法访问,但不会被收集。

这就是为什么有标记/扫描算法的原因。从高层的角度来看,它的作用是遍历对象图,从一些根对象开始并分析其关系以确定对象是否可达。该算法将确定即使Bar_1和Bar_2的RC为1,它们也不可达,因此应被视为垃圾并在某个时刻收集。

事件,听众和调度员的工作方式相同。他们不是一个特例。当你这样做时:

function test():void {
    foo1.addEventListener("someEvent",someHandler);
}

function someHandler(e:Event):void {
}

与做的一样:

function test():void {
    foo1.someProperty = this;
}

效果是foo1现在引用了this。你通常在完成后调用removeEventListener有两个原因:

1)您不再希望foo1引用this。 2)你不再想听“someEvent”事件了。

许多人坚持使用弱引用,因为他们认为你可以假装你不必打电话给removeEventListener(这显然太难......)。这是错的。 removeEventListener做了两件事,两件事都很重要。如果您想停止接收某些事件的通知,您必须告诉调度员。这真的很简单。在我看来,在大多数情况下,弱引用是不必要的。有些人主张默认使用它们;但根据我的经验,在实践中这对他们来说是一项糟糕的服务,因为它让人们更加困惑,鼓励他们编写草率的代码并给他们一种印象,你可以忽略这种语言的基本特征(这并不难工作。

现在,经过这个相当长的(但希望有建设性的)咆哮,让我们来看看你的代码:

您的精灵不会被收集,因为它有2个引用:

1)square变量 2)舞台。

第一个遵循上面的规则。第二个也是如此,但初看起来可能并不那么明显。但是如果你想一下它就有意义了。当你这样做时:

addChild(square);

square已添加到Test实例,该实例又添加到stagestage始终处于活动状态,因此如果可以从stage获得某些内容,则可以访问square。只要stage仍添加到removeChild(square) square = null; ,您就可以确定它不会被收集。

所以,如果你在某些时候做了Sean Thayne的建议:

Sprite

您的Test将可收藏。这并不影响你告诉你的{{1}}对象你想在每次输入框架时被调用的事实。而这正是发生的事情。在你不告诉它之前你不想再接收这个事件(调用removeEventListener),它会给你回电。

答案 1 :(得分:2)

Flash的垃圾收集只清除具有零引用计数或只有弱引用的元素/对象/变量。

这意味着你需要这样做。因为它真的是gc'd。

removeChild(square)
square = null;
System.gc()