r2_118
r2_118

Reputation: 650

When are DOMs removed from memory?

I am working on an application that is creating and removing a lot of DOMs. I've notice that the process memory from the browser tab continuously increases, despite the javascript heap memory remaining constant. In a test application I create and remove divs from a parent 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;
}

I've confirmed that the javascript heap is not leaking with the chrome dev tools (I'm new to them so I could have missed something). However the memory for the process continues to increase. From everything I've read I suspect that the removed doms are still in the dom heap.

Other posts also say that the browser will eventually free the memory allocated to the removed doms. In the above jsfiddle example I've hit create and delete several times. My javascript heap is steady at 4.9MB. My process memory is up to 115MB. I've waited 30 mins and it hasn't gone down at all.

Questions

  1. When are removed DOM elements completely removed from the browser process memory?
  2. Is there a way to force DOM garbage collection?
  3. Is there a tool to get more insight into what doms are marked for garbage collection? I couldn't find one in Chrome or IE.

Thanks for the help!

Edit

I have used the chrome dev tools and the javascript heap is not growing. Interestingly, the only thing that changes between the heap snapshots is an (array) object. It's my understanding that anything in parenthesis is controlled by the browser and outside of my reach. Each subsequent create->delete removes the old (array) object and creates a new one during the delete.

In timeline I can see that the javascript heap is constant and the nodes get cleaned up, but the memory as shown with (shift + esc) never goes down even after the node count drops.

enter image description here enter image description here

It seems like I'm doing everything I can to make sure I cleanup my javascript heap, but the dom cleanup is out my reach and independent of the javascript GC. Is this statement correct?

Are the removed doms part of the young generation heap? Is there a way to set a limit on this heap size? I repeated the test until I had reached 500MB and still no cleanup. I'm using Chrome 35.0.1916.114 btw.

Upvotes: 12

Views: 1261

Answers (2)

Nickolay
Nickolay

Reputation: 32063

I know you asked about Chrome, but I'll describe how it works in Firefox, hoping it might be of interest to you and other readers. Chrome may work similarly, I'm not sure.

With your testcase the Firefox memory usage does not continuously increase. Only the first time you create/remove the elements does the memory usage increase permanently. During subsequent create/remove cycles, all allocated memory is subsequently reclaimed.

At least in Firefox you can't force this memory to be deallocated without reloading the page. If you really need to allocate this much memory temporarily, you should do this in an iframe you can throw away when you're done.

Technical details follow:


In Firefox there's a tool to check the memory usage called about:memory. It breaks up the used memory by category and has controls to force-clean memory (GC/CC/Minimize memory).

Here's how the relevant bit of about:memory looks after you create/remove the DOM elements and after GC kicks in:

├──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

(If the DOM nodes were already removed from the document, but not garbage collected yet, they show up under orphan-nodes measurement.)

Most of the additional memory (requested when the DOM nodes were created) is reserved for layout.

  • Firefox, like other browsers, creates a separate rendering tree (called frame tree in Gecko) based on DOM and CSS. This is an implementation detail, completely inaccessible to the web page.
  • Frames in Gecko are allocated from arenas.
  • The layout (so-called PresShell) arenas allocate additional memory from the OS when no more frames can fit in already allocated space (this happens when you first create the 50,000 DOM elements), but don't release it back to the OS until the page is unloaded.

This behavior is based on measurements that showed that real-life web pages usually need around the same number of layout objects during their lifetime: a typical web page doesn't allocate 10,000s of frames only to destroy them and show a very simple page instead, like the testcase here does.

This memory management behavior pays off in improved speed of frame allocation/deallocation, reduced memory fragmentation, and avoiding nasty security bugs when a frame is accessed after it's destroyed.

Upvotes: 1

Olli K
Olli K

Reputation: 1760

The browser should take care of that. 113MB of memoryusage is still quite low.

But as an example, please consider this instead: 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>

Upvotes: 0

Related Questions