【问题标题】:When are DOMs removed from memory?DOM 何时从内存中删除?
【发布时间】:2014-07-18 13:23:33
【问题描述】:

我正在开发一个创建和删除大量 DOM 的应用程序。我注意到浏览器选项卡中的进程内存不断增加,尽管 javascript 堆内存保持不变。在一个测试应用程序中,我从父 div 创建和删除 div。

http://jsfiddle.net/PSxPz/2/

<button onclick="createStuff()">Create</button>
<button onclick="deleteStuff()">Delete</button>
<div id="parent"></div>

function createStuff() {
    var parentDiv = document.getElementById('parent');
    for (var i = 0; i < 50000; i++) {
        var child = document.createElement('div');
        child.id = i;
        child.textContent = i;

        parentDiv.appendChild(child);
        child = null;
    }

    parentDiv = null;
}

function deleteStuff() {
    var parentDiv = document.getElementById('parent');
    for (var i = 0; i < 50000; i++) {
        var child = document.getElementById(i);
        parentDiv.removeChild(child);
        child = null;
    }

    parentDiv = null;
}

我已经确认 chrome 开发工具没有泄漏 javascript 堆(我是他们的新手,所以我可能会遗漏一些东西)。然而,进程的内存继续增加。从我读过的所有内容中,我怀疑已删除的 dom 仍在 dom 堆中。

其他帖子还说浏览器最终将释放分配给已删除 dom 的内存。在上面的 jsfiddle 示例中,我多次点击创建和删除。我的 javascript 堆稳定在 4.9MB。我的进程内存高达 115MB。我已经等了 30 分钟,它根本没有掉下来。

问题

  1. 何时从浏览器进程内存中完全删除 DOM 元素?
  2. 有没有办法强制 DOM 垃圾回收?
  3. 是否有工具可以更深入地了解哪些 dom 被标记为垃圾回收?我在 Chrome 或 IE 中找不到。

感谢您的帮助!

编辑

我使用了 chrome 开发工具,但 javascript 堆没有增长。有趣的是,堆快照之间唯一变化的是(数组)对象。我的理解是括号中的任何内容都由浏览器控制并且超出了我的范围。每个后续的 create->delete 都会删除旧的(数组)对象并在删除期间创建一个新对象。

在时间线中,我可以看到 javascript 堆是恒定的,并且节点被清理,但 (shift + esc) 所示的内存即使在节点数下降后也不会下降。

似乎我正在尽我所能确保清理我的 javascript 堆,但 dom 清理超出了我的能力范围,并且独立于 javascript GC。这种说法正确吗?

被移除的dom是年轻代堆的一部分吗?有没有办法设置这个堆大小的限制?我重复了测试,直到达到 500MB 并且仍然没有清理。我正在使用 Chrome 35.0.1916.114 顺便说一句。

【问题讨论】:

  • 仅供参考,您不必null 输出局部变量。当函数完成时,它们将被自动处理(只要没有创建闭包来保持作用域活动——这在你的两个函数中没有)。
  • 您可以在 chrome 开发工具 > 时间线 > 开始录制中尝试,在内存选项卡中您可以看到创建/删除时堆内存的增加...。顶部还有一个按钮“收集垃圾”...当您单击它时,您可以看到堆内存使用量的减少
  • 浏览器会为你做这件事。
  • @r2_118 等待很长时间可能不会触发垃圾收集器。当浏览器的年轻代被填满时,它会进行垃圾回收。 html5rocks.com/en/tutorials/memory/effectivemanagement
  • 我刚刚注意到您正在使用 Chrome 任务管理器来诊断您的内存问题。此视图只能告诉您您的应用程序使用了大量内存。您只能通过 javascript/dom/plugin/etc 内存使用来间接控制应用程序使用的内存量。因为您的应用程序使用了大量内存,所以 chrome 给了它很多内存是合适的(它很可能会再次需要它)。如果内存是泄漏的结果,这只是一个问题。从你发布的时间线 > 内存截图来看,看起来一切都被回收了

标签: javascript html dom memory-management memory-leaks


【解决方案1】:

我知道您询问了 Chrome,但我将描述它在 Firefox 中的工作原理,希望您和其他读者可能会对它感兴趣。 Chrome 可能也有类似的工作方式,我不确定。

在您的测试用例中,Firefox 内存使用量不会持续增加。只有第一次创建/删除元素时,内存使用量才会永久增加。 在随后的创建/删除周期中,所有分配的内存随后都会被回收。

至少在 Firefox 中,您不能在不重新加载页面的情况下强制释放此内存。如果您确实需要临时分配这么多内存,您应该在一个 iframe 中执行此操作,完成后可以丢弃。

技术细节如下:


在 Firefox 中,有一个工具可以检查内存使用情况,称为 about:memory。它按类别分解已使用的内存,并具有强制清理内存(GC/CC/Minimize memory)的控件。

下面是about:memory 的相关位在您创建/删除 DOM 元素和 GC 启动后的样子:

├──34.34 MB (03.30%) -- window(http://fiddle.jshell.net/PSxPz/2/show/)
│  ├──26.54 MB (02.55%) -- layout
│  │  ├──13.95 MB (01.34%) -- (8 tiny)
│  │  │  ├───7.63 MB (00.73%) ── line-boxes
│  │  │  ├───4.00 MB (00.38%) ── pres-contexts
│  │  │  ├───2.26 MB (00.22%) ── pres-shell
│  │  │  ├───0.04 MB (00.00%) ── style-structs
│  │  │  ├───0.01 MB (00.00%) ── rule-nodes
│  │  │  ├───0.01 MB (00.00%) ── style-contexts
│  │  │  ├───0.00 MB (00.00%) ── style-sets
│  │  │  └───0.00 MB (00.00%) ── text-runs
│  │  └──12.59 MB (01.21%) -- frames
│  │     ├───7.25 MB (00.70%) ── nsBlockFrame
│  │     ├───5.34 MB (00.51%) ── nsTextFrame
│  │     └───0.00 MB (00.00%) ── sundries
│  └───7.80 MB (00.75%) -- (4 tiny)
│      ├──7.51 MB (00.72%) ++ dom
│      ├──0.29 MB (00.03%) ++ js-compartment(http://fiddle.jshell.net/PSxPz/2/show/)
│      ├──0.00 MB (00.00%) ── style-sheets
│      └──0.00 MB (00.00%) ── property-tables

(如果 DOM 节点已从文档中删除,但尚未进行垃圾回收,则它们会显示在 orphan-nodes 测量值下。)

大部分额外内存(在创建 DOM 节点时请求)保留用于布局。

  • Firefox 与其他浏览器一样,会创建一个单独的rendering tree (called frame tree in Gecko) based on DOM and CSS。这是一个实现细节,网页完全无法访问。
  • Gecko 中的帧从arenas 分配。
  • 布局(所谓的 PresShell)arenas 会在已经分配的空间无法容纳更多帧时从操作系统分配额外的内存(当您第一次创建 50,000 个 DOM 元素时会发生这种情况),但不要将其释放回操作系统直到页面被卸载。

这种行为is 基于测量结果表明现实生活中的网页在其生命周期中通常需要大约相同数量的布局对象:一个典型的网页不会分配 10,000 帧只是为了销毁它们并显示一个非常简单的页面,就像这里的测试用例一样。

这种内存管理行为在提高帧分配/解除分配的速度、减少 memory fragmentation 以及避免在销毁帧后访问时避免严重的安全漏洞方面得到了回报。

【讨论】:

    【解决方案2】:

    浏览器应该注意这一点。 113MB 的内存使用率仍然很低。

    但作为一个例子,请考虑这个: http://jsfiddle.net/gildean/PSxPz/3/

    <button class="create">Create</button>
    <button class="delete">Delete</button>
    <div id="parent"></div>
    
    <script>
    var parentDiv = document.querySelector('#parent');
    var actions = {
        create: function createStuff() {
            var frag = document.createDocumentFragment();
            for (var i = 0; i < 10000; i++) {
                var child = document.createElement('div');
                child.id = i;
                child.textContent = i;
                frag.appendChild(child);
            }
            parentDiv.appendChild(frag);
        },
        delete: function deleteStuff() {
            while (parentDiv.children.length) parentDiv.removeChild(parentDiv.firstChild);
        }
    };
    
    Array.prototype.forEach.call(document.querySelectorAll('button'), function addListener(el) {
        el.addEventListener('click', function handler(event) {
            console.log(event.target.textContent + '!');
            actions[event.target.className]();
        });
    });
    </script>
    

    【讨论】:

    • 在谈到内存使用时,需要注意的是 ie8 不支持textContent。因此在元素上设置textContent 将创建一个expando 属性,这将导致内存泄漏。此外,handler 回调在范围内有 el DOM 元素,这可能导致泄漏。当然,这些按钮永远不需要清理,因为它们永远不会被删除,但最好将 handler 函数移到 foreach 循环之外或在最后移动 el = null;
    猜你喜欢
    • 2015-01-29
    • 2015-11-21
    • 2013-04-30
    • 1970-01-01
    • 1970-01-01
    • 2023-01-30
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    相关资源
    最近更新 更多