什么时候从内存中删除DOM?

时间:2014-05-29 05:49:25

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

我正在开发一个创建和删除大量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;
}

我已经确认javascript堆没有使用chrome dev工具泄漏(我是他们的新手所以我可能错过了一些东西)。然而,该过程的记忆继续增加。从我读过的所有内容中,我怀疑移除的doms仍然在dom堆中。

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

问题

  1. 何时删除了从浏览器进程内存中完全删除的DOM元素?
  2. 有没有办法强制进行DOM垃圾回收?
  3. 是否有工具可以更深入地了解哪些doms标记为垃圾收集?我在Chrome或IE中找不到一个。
  4. 感谢您的帮助!

    修改

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

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

    enter image description here enter image description here

    似乎我正在尽我所能确保清理我的javascript堆,但是dom清理是我无法实现的,并且独立于javascript GC。这个陈述是否正确?

    移除的doms是否是年轻一代的一部分?有没有办法设置这个堆大小的限制?我重复测试,直到我达到500MB仍然没有清理。我正在使用Chrome 35.0.1916.114 btw。

2 个答案:

答案 0 :(得分:1)

我知道你问过Chrome,但我会在Firefox中描述它是如何运作的,希望你和其他读者可能会感兴趣。 Chrome可能会有类似的工作,我不确定。

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

至少在Firefox中,您无法在不重新加载页面的情况下强制释放此内存。如果你真的需要分配这么多内存暂时,你应该在iframe中执行此操作,以便在完成后丢弃。

技术细节如下:

在Firefox中,有一种工具来检查名为about:memory的内存使用情况。它按类别分解已用内存,并具有强制清理内存的控制(GC / CC /最小化内存)。

以下是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)竞技场在操作系统中分配了额外的内存,当没有更多的帧可以放入已经分配的空间时(当你第一次创建50,000个DOM元素时会发生这种情况),但不要将它释放回来操作系统直到页面被卸载。

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

这种内存管理行为可以提高帧分配/解除分配的速度,减少memory fragmentation,并在帧被破坏后访问时避免令人讨厌的安全漏洞。

答案 1 :(得分:0)

浏览器应该照顾它。 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>