Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTMLLoader / StageWebView Memory Leak

When creating an instance of StageWebView, if you switch between multiple sites you'll notice that the amount of memory used slowly goes up. This isn't an issue if you're viewing 2/3 sites, but when it comes to creating an AIR application with a built in browser, I've now hit a brick wall in terms of memory management.

No matter what I do

  • StageWebView.dispose() after each call
  • Reset stage

The problem persists. Each load of a web page will increase memory by 3mb, which gradually increases to over 1gb and crashes the application.

I have no event listeners on the StageWebView instance. I hold absolutely no references to it. The memory just does not fully reset after the 2nd URL loads.

This can be seen by running the below in AIR:

package kazo
{
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import flash.media.StageWebView;
    import mx.controls.Button;
    import mx.core.UIComponent;
    import flash.system.System;

    /**
     * ...
     * @author KM
     */
    public class Controller extends UIComponent
    {

        private var stageWeb:StageWebView;
        private var url:uint = 0;

        private const URL_ARRAY:Array = [
            'http://www.mmo-champion.com/',
            'http://www.bbc.co.uk/news/',
            'http://www.twitch.tv/riotgames/',
            'http://www.stackoverflow.com/'
        ]

        /**
         * 
         */
        public function Controller() 
        {
            addEventListener(Event.ADDED_TO_STAGE, init);
        }

        /**
         * 
         * @param   e
         */
        private function init(e:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);

            stageWeb = new StageWebView();
            stageWeb.stage = stage;
            stageWeb.viewPort = new Rectangle(0, 80, width, height - 50);

            var btn:Button = new Button();
            addChild(btn);
            btn.label = 'Load URL';
            btn.x = 0;
            btn.y = 10;;
            btn.width = 100;
            btn.height = 30;
            btn.addEventListener(MouseEvent.CLICK, load);

            btn = new Button();
            btn.label = 'Try to GC';
            btn.x = 150;
            btn.y = 10;;
            btn.width = 100;
            btn.height = 30;
            addChild(btn);
            btn.addEventListener(MouseEvent.CLICK, tryGC);

            /// 26,576k
        }

        /**
         * 
         * @param   e
         */
        private function load(e:MouseEvent):void {
            if (!stageWeb) {
                stageWeb = new StageWebView();
                stageWeb.stage = stage;
                stageWeb.viewPort = new Rectangle(0, 80, width, height - 50);
            }               

            stageWeb.loadURL(URL_ARRAY[url % 4]);

            url++;
        }

        /**
         * 
         * @param   e
         */
        private function tryGC(e:MouseEvent):void {
            stageWeb.stage = null;
            stageWeb.viewPort = null;
            stageWeb.dispose();
            stageWeb = null;
            System.gc();
        }

    }

}

Does anyone have any experience with this?

like image 375
Kevin McGowan Avatar asked Jul 11 '26 05:07

Kevin McGowan


1 Answers

I did some minor changes to your code to make it compilable with AIR 14 SDK (ASC 2.0, no flex), I build & tested the air application with

mxmlc -optimize=true +configname=air Controller.as && adl Controller.xml

and watched memory usage with Adobe Scout

Memory after GC was stable, stabilizing at 14481 kB

You may add -advanced-telemetry=true option to mxmlc to track allocations/deallocations precisely, just think to push the "hide cleaned objects" switch and select a range beetween two GC. You will see some object are not released immediatly, but you'll also see that if you select several of those ranges at the same time, those temporary leaks do not add up, which explains why memory does not increase constantly, in a GC environment, memory leaks are not really memory leaks

How do you measure memory usage ?

I checked memory usage of the adl process, it was higher ( > 100MB ) and less stable, but still stabilizing somewhere between 110 and 120MB after GC. In any case, it was definitely not looking like it was going to increase up to 1 GB

Just remember that ActionScript GC is lazy and keeps pool of objects for future reuse, any application doing more than hello world will have sometimes strange memory behavior, which is part of the way AVM2 is working.

package 
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import flash.media.StageWebView;
    import flash.system.System;
    import flash.text.TextField;

    /**
     * ...
     * @author KM
     */
    public class Controller extends Sprite
    {

        private var stageWeb:StageWebView;
        private var url:uint = 0;

        private const URL_ARRAY:Array = [
            'http://www.mmo-champion.com/',
            'http://www.bbc.co.uk/news/',
            'http://www.twitch.tv/riotgames/',
            'http://www.stackoverflow.com/'
        ]

        /**
         * 
         */
        public function Controller() 
        {
            addEventListener(Event.ADDED_TO_STAGE, init);
        }

        /**
         * 
         * @param   e
         */
        private function init(e:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);

            stageWeb = new StageWebView();
            stageWeb.stage = stage;
            stageWeb.viewPort = new Rectangle(0, 80, stage.stageWidth, stage.stageHeight - 50);

            var btn:TextField = new TextField();
            btn.selectable = false;
            addChild(btn);
            btn.text = 'Load URL';
            btn.x = 0;
            btn.y = 10;;
            btn.width = 100;
            btn.height = 30;
            btn.addEventListener(MouseEvent.CLICK, load);

            btn = new TextField();
            btn.selectable = false;
            btn.text = 'Try to GC';
            btn.x = 150;
            btn.y = 10;;
            btn.width = 100;
            btn.height = 30;
            addChild(btn);
            btn.addEventListener(MouseEvent.CLICK, tryGC);

            /// 26,576k
        }

        /**
         * 
         * @param   e
         */
        private function load(e:MouseEvent):void {
            if (!stageWeb) {
                stageWeb = new StageWebView();
                stageWeb.stage = stage;
                stageWeb.viewPort = new Rectangle(0, 80, stage.stageWidth, stage.stageHeight - 50);
            }               

            stageWeb.loadURL(URL_ARRAY[url % 4]);

            url++;
        }

        /**
         * 
         * @param   e
         */
        private function tryGC(e:MouseEvent):void {
            stageWeb.stage = null;
            stageWeb.viewPort = null;
            stageWeb.dispose();
            stageWeb = null;
            System.gc();
        }

    }

}
like image 187
jauboux Avatar answered Jul 13 '26 16:07

jauboux



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!