这段代码会导致内存泄漏吗?

11

这会导致内存泄漏吗?

var mc:MovieClip ; //<<<<<<< OUTSIDE LOOP

for ( var i=0 ; i< 1000 ; i++)
{
   mc = new MovieClip() ;
   mc.addEventListener( MouseEvent.CLICK , onClick) ;
}

那这个呢?

for ( var i=0 ; i< 1000 ; i++)
{
  var mc:MovieClip ; //<<<<<<< INSIDE LOOP
   mc = new MovieClip() ;
   mc.addEventListener( MouseEvent.CLICK , onClick) ;
}

"removeEventListener" 在以上代码中未被使用,因此我认为两个代码块都会导致内存泄漏。


1
目前,两者都不会导致内存泄漏,因为您可能需要使所有1000个电影剪辑可点击。只有当您完成了电影剪辑并希望将它们从内存中删除时,事件侦听器问题才会出现。Ascention的答案是正确的,但单词“would”很重要。 - crooksy88
大家好,我承认错误。我再也不会相信Adobe的文档了。请查看我的更新答案。我的当前答案是错误的。 - user562566
3个回答

6
更新,正确答案:

我原来的回答是错误的,我诚挚地道歉。我会保留所有傲慢的评论和信息,以便这种耻辱能永远提醒我再也不相信Adobe说的任何话了。当前文档现在说:

"如果您不再需要事件侦听器,请通过调用removeEventListener()来删除它,否则可能会导致内存问题。事件侦听器不会自动从内存中删除,因为垃圾收集器不会在分派对象存在时将侦听器删除(除非useWeakReference参数设置为true)。

请注意,事件侦听器无论是否有弱引用,只要分派对象首先被移除,它们就会被垃圾收集,所以在这两种情况下,它们都不会导致内存泄漏。我请求OP取消我的答案并将信用/正确答案+赞给@Malyngo。

原始(错误)答案和(错误)信息如下:

两者都会。绑定事件侦听器会创建对原始对象的强引用,因此垃圾收集器不会清理它。您需要显式地删除事件侦听器或将其指定为弱引用,这应该是addEventListener的参数之一。 对于那些认为侦听器不会阻止其他对象被垃圾收集的人 http://gingerbinger.com/2010/07/actionscript-3-0-events-the-myth-of-useweakreference/ 文章摘要:
“想象一下,我们的玩家死了,我们希望他被清理掉。但是,事件侦听器创建了一个从舞台到玩家的引用。舞台是最顶层的显示对象,始终可以访问。因此,当标记-清除过程运行时,即使我们清除了所有其他引用并将其从显示列表中删除,该事件侦听器也允许垃圾收集器从舞台跳到我们的玩家对象。”
因此,仍然存在至少一种情况,即单独强绑定的事件侦听器可能会防止对象被收集
最佳实践解决方案:
1)从显示列表中删除它。
2)如果它是MovieClip,请告诉它停止()。
3)删除对象创建的任何事件侦听器。
4)通过将它们设置为null清除父对象中的任何引用。
再次更新 一个内存泄露不一定意味着应用程序的内存会不断增长。当内存被分配并在应用程序生命周期内持久存在时(本该被回收),就可能出现内存泄漏。像这个测试代码一样的情况很难被发现。但是,如果在一个小时的游戏中发生了N次这样的情况,我保证它会表现出来。我曾经编写过一种加密算法,也遇到了同样的情况。一段时间后,我的应用程序开始以每秒10帧或更少的速度运行,因为虚拟机占用了大量内存,但实际上不再使用它,但仍在管理它。

6

您的1000个Movieclips将在onClick函数中引用到您。而不是相反。

如果您的问题是您的1000个Movieclips是否会被垃圾回收:如果它们没有任何其他引用,它们最终会被回收。

另一方面,您的movieclips对于您的onClick函数的引用将使其保持活动状态(以及可能属于该对象的对象)。如果这些MC具有任何其他引用,那么它们将保持活动状态。

以下代码:

mc.addEventListener(MouseEvent.CLICK , function(ev:Event):void{  trace("I am only a poor anonymous function");  }, false, 0, true);

由于没有强引用,您的监听函数很快就会被垃圾回收。

如果您要将事件侦听器添加到舞台,则将 useWeakReference 设置为true可能非常相关。

stage.addEventListener(MouseEvent.CLICK, someObjectBelowIntheDisplayList.listenerFunction);

上述代码将保持具有监听器功能的对象的存在,即使它没有其他引用。
someObjectBelowIntheDisplayList.addEventListener(MouseEvent.CLICK, stage.onClick)

上面的代码并不会使得你的someObjectBelowIntheDisplayList对象保持活跃状态。它有一个对舞台(stage)的引用,但舞台并没有引用someObjectBelowIntheDisplayList对象。

编辑:请尝试下面的代码:

import flash.display.MovieClip;
import flash.events.Event;

var mc:MovieClip ; //<<<<<<< OUTSIDE LOOP

function enterframe(ev:Event):void
{
    for ( var i=0 ; i< 1000 ; i++)
    {
       mc = new MovieClip() ;
       mc.onClick = function(ev:Event){};
       // Use one of the following lines, comment out the other one
       //mc.addEventListener( MouseEvent.CLICK , onClick) ; // no memory leak
       stage.addEventListener(MouseEvent.CLICK, mc.onClick); // memory will rise up and up
    }
}

this.addEventListener(Event.ENTER_FRAME, enterframe);

function onClick(ev:Event):void
{

}

这段代码明确证明了我的说法:使用mc.addEventListener将不会增加内存消耗。在我的系统上,它将保持在20MB左右。 当使用stage.addEventListener并将mc.onClick作为侦听器函数时,内存消耗将每帧增加。


我提供的测试代码在每一帧都会不断地创建1000个Movieclips。如果存在内存泄漏,那么内存消耗必须增加,因为所有这些对象都不会被收集。但它们确实被收集了。通过将Movieclips添加为舞台的侦听器,就会出现内存泄漏。引用始终是从调度程序到侦听器,而不是相反。因此,如果没有对调度程序的引用,它将被销毁,无论它可能有多少侦听器。 - Malyngo
您是100%正确的先生。我认错了。我更新了我的答案并添加了更多信息。自从上次我阅读文档以来,似乎Adobe已经添加了新信息。我还要求将我的答案标记为不正确,并将信用归给您。 - user562566
添加 ENTER_FRAME 事件的意义是什么?循环不足以满足需求吗? - Vishwas
@Vishwas Gagrani 这个循环将创建1000个Movieclips,然后不会发生任何其他事情。如果存在内存泄漏,那些1000个剪辑将不会被垃圾回收,但也可能不会被GC,因为没有必要,因为内存不再增长。通过使用ENTER_FRAME事件并在每帧上创建越来越多的movieclips,我确保利用了内存泄漏。如果有一个,内存消耗将不断增长。如果没有,则GC将收集这些movieclips并处理它们,使内存使用保持在恒定水平。 - Malyngo

1

这两个示例是相同的,因为ActionScript基于没有块作用域的ECMAScript 3。

编辑:让我更具体一些:ActionScript中没有块作用域,但有函数作用域。

关于内存泄漏,对象将会保留在内存中。


2
“没有块级作用域”是错误的。如果你的意思是说作用域与内存分配和释放无关,那就是另一回事了,但仅仅说没有块级作用域本身是错误的。 - user562566
我认为只要您不将函数视为块级作用域并将其视为完全独立的作用域,那么这个说法实际上是正确的(再次强调,从技术上讲是正确的)。 - WORMSS
6
我认为JNissi想要表达的是,AS3采用变量提升机制,因此在循环内部或外部声明变量具有相同的效果;) - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接