HTMLLoader / StageWebView内存泄漏

3
创建StageWebView实例后,如果在多个站点之间切换,您会注意到使用的内存量会缓慢增加。如果您只查看2/3个站点,则这不是问题,但是当创建带有内置浏览器的AIR应用程序时,我现在在内存管理方面遇到了瓶颈问题。
无论我做什么
- 每次调用后都会StageWebView.dispose() - 重置舞台
问题仍然存在。每次加载网页都会增加3mb的内存,逐渐增加到超过1gb并崩溃应用程序。
我没有在StageWebView实例上设置任何事件侦听器。我绝对没有持有对它的引用。第二个URL加载后,内存就无法完全重置。
在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();
        }

    }

}

有没有人对这个有经验?


1
使用类似于setInterval的过时全局方法和匿名函数并不是展示内存泄漏的好方式,这更多地展示了您的代码风格,可能会导致内存泄漏。 - BotMaster
我会在周末写一些合法的代码。我不能发布生产代码。感觉有些缓存仍然留在内存中,我找不到解决方法。 - Kevin McGowan
StageWebView自AIR 2.5以来就存在了,如果它很容易引起内存泄漏,我认为我们现在应该已经知道了。 - BotMaster
@BotMaster 已更新。运行上述代码并查看加载时的内存使用情况。然后,单击加载 URL 4 次,并让每个 URL 完全加载。然后,单击运行 GC。现在比较这两个内存值。 - Kevin McGowan
根据我在谷歌上找到的信息... https://forums.adobe.com/message/4008374 如果在处理之前添加一个重新加载调用会发生什么? - Pimgd
显示剩余4条评论
2个回答

2

我对你的代码进行了一些小改动,以使其能够与AIR 14 SDK(ASC 2.0,无flex)编译。我使用以下命令构建并测试了air应用程序:

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

并使用Adobe Scout观察内存使用情况。

GC之后的内存稳定在14481 kB。

您可以在mxmlc中添加-advanced-telemetry=true选项,以精确跟踪分配/释放,只需记得按下“隐藏已清理对象”开关并选择两个GC之间的范围。您将看到某些对象不会立即释放,但是如果同时选择这些范围,那么这些临时泄漏不会累加,这就解释了为什么在GC环境中,内存泄漏并非真正的内存泄漏。

如何测量内存使用情况?

我检查了adl进程的内存使用情况,它更高(>100MB),并且不太稳定,但是在GC之后仍稳定在110到120MB之间。无论如何,它绝对不会增加到1 GB。

请记住,ActionScript GC是懒惰的,并保留对象池以供将来重用。任何执行比“hello world”更多的应用程序都会有时候出现奇怪的内存行为,这是AVM2工作方式的一部分。

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();
        }

    }

}

问题出现在你将数组从4个域扩展到400个域(或允许域条目)时。因为内存会稳定下来,但不会重置,所以在达到最大内存之前,你永远无法遍历所有400个域。我曾经成功访问的最大URL数量是117个,在此时内存已经达到了1.15GB。尽管如此,我知道(并且同意)Scout报告的内存与Windows任务管理器不同。不幸的是,似乎任务管理器是正确的,因为一旦你开始接近1GB的标记,它很快就会崩溃。 - Kevin McGowan
其实,你现在说的很有道理。经过重复测试,仅使用这4个域名,虽然最初只有33mb,但在加载完所有4个域名后,它会增加到95mb,并且在GC之后总是会降回到95mb。我的问题是,仅仅通过访问URL就存储了那么多数据,这对我的项目来说是不可行的。这是AVM2中GC工作方式固有的缺陷吗?这意味着无法规避吗?目前,我有一个BAT文件,在访问100个URL后调用它关闭应用程序并重新打开它,以便用户可以从上次离开的地方继续(这不是一个好的流程)。 - Kevin McGowan
实际上,我进行了一个快速实验,将URL列表更改为只有google.com,删除了尝试gc按钮,改进了舞台的调整大小以启用最大化窗口大小,并将其用作基本的单选项卡浏览器一整天。它运行得非常好,我没有遇到任何崩溃,内存消耗从未超过200MB,与传统浏览器相比相当合理,我使用了几个重型Facebook Flash游戏,使用Stage3D等。在我的看法中,只要保持相同的StageWebView实例,就不会出现内存问题。 - jauboux

0

这只是一个路人甲的闲言碎语..注意:我还没有测试你的代码,因为我此刻没有Flash访问权限。我的胡言乱语并不完全正确(我知道这是个WTF?但请继续阅读,希望里面有一些有用的东西..)

在你的函数tryGC(e:MouseEvent):void中,你设置了stageWeb = null;,这很好,但是在你的函数load(e:MouseEvent):void中,你说"如果stageWeb为空,则创建一个新的"。既然你先前设置了Flash考虑那一个null,它顺从地在内存中创建了一个新的(旧的内存印迹可能还没有被垃圾回收(只是排队等待)),因此看起来像是内存使用量增加了。

整个“tryGC”事情有点缺陷,如果你接着要立即加载其他页面。垃圾回收(可能)从未发生,因为还有一个鼠标事件潜伏在那里,说“if (!stageWeb) { //etc }”,所以基本上你正在将其状态切换为“null”,但它不能真正被删除,因为其他东西正在使用对它的引用,而且这个东西随时可能在未来被调用,所以它就一直停留在内存中(带有“null”状态)。
可能的解决方案:
1)在您的函数“tryGC”中删除用于加载URL的鼠标侦听器。
private function tryGC(e:MouseEvent):void {
stageWeb.stage = null;
stageWeb.viewPort = null;
stageWeb.dispose();
stageWeb = null;
btn.removeEventListener(MouseEvent.CLICK, load);
System.gc();
}

现在等待几秒钟,检查内存使用情况是否降低了?如果有帮助,请在尝试GC之前加载至少10个以上的URL,因为您将无法通过点击加载URL。这只是为了测试鼠标监听器是否对垃圾收集器造成了影响。

2) 你真正需要的是一种清除/覆盖当前URL内容/缓存的方法...而不是重新创建新的HTML页面视图。在这种情况下,那样做没有任何帮助。我认为你的问题也源于此...历史前进/后退...我怀疑每次加载URL都会添加到历史记录中,而历史记录本身更像是一个“内容缓存”,而不仅仅是一个“包含先前URL的字符串”。
考虑到你对BAT文件的计划...嗯,那将打开100个URL,在内存中依次缓存它们,但你的视口一次只显示一个项目(其他99个页面已经完全加载并准备好立即显示..)。我不知道有没有解决这个问题的方法。没有stageWeb.unloadURL();甚至stageWeb.clearCache();我没有找得太仔细,但这个星球上肯定有人知道一个诀窍(希望)并写了博客...


这个不起作用,但是你的回答有效地表明它是一个缓存。只是似乎绝对没有办法清除它。 - Kevin McGowan

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