加载图像后创建内联函数导致内存泄漏

Creating inline function after loading an image causes memory leak

我在分析游戏项目中的意外内存泄漏时发现了一些奇怪的结果。我正在使用 Adob​​e Scout 进行分析,并消除了所有其他因素,如八哥、纹理或我们的加载库。我减少了代码以简单地加载一个 png 并立即在其 complete 事件上分配一个空的内联函数。

加载 png 会默认分配图像,如果在加载后不执行任何操作,gc 会清除该图像。但是创建一个内联函数似乎可以防止该图像以某种方式被垃圾收集。我的测试代码是;

public class Main extends Sprite 
{
    private var _callbacks:Array = new Array();

    public function Main() 
    {
        load("map.png", onPngLoaded);
    }

    private function onPngLoaded(bitmap:Bitmap):void 
    {
        _callbacks.push(function():void { });
    }

    public function load(url:String, onLoaded:Function):void 
    {
        var loader:Loader = new Loader;

        var completeHandler:Function = function(e:Event):void {
            loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, completeHandler);
            onLoaded(loader.content);
        }

        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);

        loader.load(new URLRequest(url));   
    }
}

如果删除创建内联函数的代码;

    private function onPngLoaded(bitmap:Bitmap):void 
    {
        // removed the code here!
    }

gc 工作并从内存中清除图像。

由于对此没有合理的解释,我怀疑是 flash / as3 错误。我很高兴听到任何测试我的代码并得到相同结果的评论。

注意:要进行测试,请将空 as3 项目的主要 class 替换为我的代码并导入包。您可以加载任何 png。我正在使用 flashdevelop、flex-sdk 4.6.0 和 flash player 14。

当您创建一个内联函数时,所有局部变量都会与它一起存储在全局范围内。所以在这种情况下,这将包括 bitmap 参数。

有关详细信息,请参阅: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f54.html

相关部分如下:

Any time a function begins execution, a number of objects and properties are created. First, a special object called an activation object is created that stores the parameters and any local variables or functions declared in the function body....Second, a scope chain is created that contains an ordered list of objects that Flash Player or Adobe AIR checks for identifier declarations. Every function that executes has a scope chain that is stored in an internal property. For a nested function, the scope chain starts with its own activation object, followed by its parent function’s activation object. The chain continues in this manner until it reaches the global object.

这是为什么在大多数情况下最好避免使用 inline/anonymous 函数的另一个原因。

所以使用 asc2,Flash/Air 19:是的,我得到了与您看到的相同的结果,但是由于匿名函数持有全局引用,我希望如此(就像我最初的评论所述)。

我根据 Adob​​e 的 GC 技术文章和公告以我的风格重写了它,并且没有看到泄漏,因为所有全局引用都被删除了。

一个cut/paste AIR示例:

package {

    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.display.Sprite;
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.system.System;
    import flash.utils.Timer;
    import flash.events.TimerEvent;

    public class Main extends Sprite {
        var timer:Timer;
        var button:CustomSimpleButton;
        var currentMemory:TextField;
        var highMemory:TextField;
        var hi:Number;

        var _callbacks:Array = new Array();

        public function Main() {
            button = new CustomSimpleButton();
            button.addEventListener(MouseEvent.CLICK, onClickButton);
            addChild(button);
            currentMemory = new TextField();
            hi = System.privateMemory;
            currentMemory.text = "c: " + hi.toString();
            currentMemory.x = 100;
            addChild(currentMemory);
            highMemory = new TextField();
            highMemory.text = "h: " + hi.toString();
            highMemory.x = 200;
            addChild(highMemory);
            timer = new Timer(100, 1);
            timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
            timer.start();
        }

        function timerHandler(e:TimerEvent):void{
            System.pauseForGCIfCollectionImminent(.25);
            currentMemory.text = "c: " + System.privateMemory.toString();
            hi = System.privateMemory > hi ? System.privateMemory : hi;
            highMemory.text = "h: " + hi.toString();
            timer.start();
        }

        function onClickButton(event:MouseEvent):void {
            for (var i:uint = 0; i<100; i++) {
                //load("foobar.png", onPngLoaded);
                load2("foobar.png");
            }
        }

        private function onPngLoaded2(bitmap:Bitmap):void {
            var foobarBitMap:Bitmap = bitmap; // assuming you are doing something
            foobarBitMap.smoothing = false;   // with the bitmap...
            callBacks(); // not sure what you are actually doing with this
        }
        private function callBacks():void {
            _callbacks.push(function ():void {
            });
        }

        public function completeHandler2(e:Event):void {
            var target:Loader = e.currentTarget.loader as Loader;
            // create a new bitmap based what is in the loader so the loader has not refs after method exits
            var localBitmap:Bitmap = new Bitmap((target.content as Bitmap).bitmapData);
            onPngLoaded2(localBitmap);
        }

        public function load2(url:String):void {
            var loader2:Loader = new Loader;
            loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler2, false, 0, true);
            loader2.load(new URLRequest(url));
        }
    }
}

import flash.display.Shape;
import flash.display.SimpleButton;

class CustomSimpleButton extends SimpleButton {
    private var upColor:uint   = 0xFFCC00;
    private var overColor:uint = 0xCCFF00;
    private var downColor:uint = 0x00CCFF;
    private var size:uint      = 80;

    public function CustomSimpleButton() {
        downState      = new ButtonDisplayState(downColor, size);
        overState      = new ButtonDisplayState(overColor, size);
        upState        = new ButtonDisplayState(upColor, size);
        hitTestState   = new ButtonDisplayState(upColor, size * 2);
        hitTestState.x = -(size / 4);
        hitTestState.y = hitTestState.x;
        useHandCursor  = true;
    }
}

class ButtonDisplayState extends Shape {
    private var bgColor:uint;
    private var size:uint;

    public function ButtonDisplayState(bgColor:uint, size:uint) {
        this.bgColor = bgColor;
        this.size    = size;
        draw();
    }

    private function draw():void {
        graphics.beginFill(bgColor);
        graphics.drawRect(0, 0, size, size);
        graphics.endFill();
    }
}