【问题标题】:HTMLLoader / StageWebView Memory LeakHTMLLoader / StageWebView 内存泄漏
【发布时间】:2014-07-08 22:07:06
【问题描述】:

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

    }

}

有人有这方面的经验吗?

【问题讨论】:

  • 使用不推荐使用的全局方法(如 setInterval)和使用匿名函数很难证明内存泄漏。也许这更像是一种显示代码风格的方式,这可能会导致内存泄漏。
  • 周末我会写一些合法的代码。我无法发布生产代码。感觉有些缓存留在内存中,我无法找到解决方法。
  • StageWebView 自 AIR 2.5 以来一直存在,如果它如此容易产生内存泄漏,我想我们现在应该知道了。
  • @BotMaster 已更新。运行上面的代码并查看加载时的内存使用情况。然后,单击加载 URL 4 次,让每个 URL 完全加载。然后,单击运行 GC。现在比较两个内存值。
  • 作为比较,我在启动时得到 25,460k,在 GC 后得到 75,600k。

标签: actionscript-3 air


【解决方案1】:

我对您的代码做了一些小改动,使其可以与 AIR 14 SDK(ASC 2.0,无 flex)编译,我构建并测试了 air 应用程序

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

并使用 Adob​​e 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 标记,它很快就会崩溃。
  • 其实你现在说的很有道理。重复测试后,仅使用这 4 个域,虽然它最初从 33mb 开始,但在所有 4 个域都加载后它确实增加到 95mb,并且在 GC 之后总是会降低到 95mb。我的问题是,仅仅通过点击 URL 来存储 那么多 数据对于我的项目来说是不可行的。这是否只是 GC 在 AVM2 中的工作方式的固有缺陷,这意味着它不能被规避?目前我有一个 BAT 文件,该文件在 100 个 URL 后调用,该文件关闭应用程序并在用户离开的地方重新打开它(这不是一个好的过程)。
  • 其实,我做了一个快速的实验,将 url 列表更改为只是 google.com 删除了 try gc 按钮,改进了舞台大小以使窗口大小最大化,并将其用作基本单曲标签浏览器整天。它运行得很好,我没有一次崩溃,内存消耗从未超过 200MB,这与经典浏览器相比是相当合理的,我尝试了一些使用 Stage3D 的重型 facebook flash 游戏等。从我的角度来看,只要你保持相同的 StageWebView 实例,没有内存问题
【解决方案2】:

这只是一个随机路人的想法漫谈。注意:我没有测试运行您的代码,因为我目前无法访问 Flash。我也不支持我漫无边际的 100%(我知道这是一个 WTF?但请继续阅读,希望那里有一些有用的东西..)

在你的函数tryGC(e:MouseEvent):void 中设置stageWeb = null; 这很好,但在你的函数load(e:MouseEvent):void 中你说“如果stageWeb 为空,则创建一个新的”。好吧,既然您之前将 Flash 设置为考虑一个 null,它会乖乖地在内存中创建一个新的(旧的内存印记可能还没有被垃圾收集(只是在它的队列中)因此看起来 ram 使用量增加了。

如果您之后要立即加载其他页面,那么整个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 页面视图。在这种情况下,这没有任何帮助。我认为您的问题也源于此...History Forward/Back...我怀疑每个 url 加载都会添加到历史记录中,而历史记录又更像是“内容缓存”,而不仅仅是“带有先前 URL 的字符串” .
考虑到您对 BAT 文件的计划...好吧,这将打开 100 个 url,依次缓存它们,但是您的视口一次只显示一个项目(内存中保存了 99 个其他页面,已完全加载并准备好即时使用展示..)。我不知道解决方法。没有stageWeb.unloadURL(); 甚至没有stageWeb.clearCache(); 我看起来不够努力,但是这个星球上的某个人知道一个技巧(并且希望)在博客上发布...

【讨论】:

  • 这不起作用,但你的回答有效地说它是一个缓存是有道理的。似乎完全没有办法清除它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 2013-01-20
  • 2011-10-31
  • 2019-08-10
  • 2013-06-24
  • 2011-03-22
  • 2015-04-20
相关资源
最近更新 更多