42

I’m using the chrome dev tools to work out if there is a memory leak in some JS code. The memory timeline looks good with memory being reclaimed as expected.

enter image description here

However, the memory snapshot is confusing because it appears like there is a leak because there are entries under “Detached DOM Tree”.

Is the stuff under “Detached DOM Tree” just waiting to be garbage collected or are these real leaks?

Also does anyone know how to find out what function is holding on to a reference to a detached element?

enter image description here

Carl Rippon
  • 4,553
  • 8
  • 49
  • 64
  • 1
    I found that the stuff in the “Detached DOM Tree” were document fragments cached by jQuery to improve performance – Carl Rippon Aug 15 '12 at 11:27

3 Answers3

34

Those elements are being referenced in your code but they are disconnected from the page's main DOM tree.

Simple example:

var a = document.createElement("div");

a references a disconnected element now, it cannot be GC'd when a is still in scope.

If the detached dom trees persist in memory then you are keeping references to them. It is somewhat easy with jQuery to do this, just save a reference to a traversed result and keep that around. For example:

var parents = $("span").parent("div");
$("span").remove();

Now the spans are referenced even though it doesn't appear you are referencing them anyhow. parents indirectly keeps references to all the spans through the jQuery .prevObject property. So doing parents.prevObject would give the object that references all the spans.

See example here http://jsfiddle.net/C5xCR/6/. Even though it doesn't directly appear that the spans would be referenced, they are in fact referenced by the parents global variable and you can see the 1000 spans in the Detached DOM tree never go away.

Now here's the same jsfiddle but with:

delete parents.prevObject

And you can see the spans are no longer in the detached dom tree, or anywhere for that matter. http://jsfiddle.net/C5xCR/7/

Esailija
  • 138,174
  • 23
  • 272
  • 326
  • +1 for mentioning global variables, didn't think of them but they could very well be the cause of many detached DOM references – Elias Van Ootegem Aug 13 '12 at 08:12
  • @EliasVanOotegem it doesn't necessarily have to be global, just something that is persistently kept around in a closure or object property or anything. My main point was that it's not immediately obvious how traversed jQuery keeps all the old references in memory as long as the latest traversed jQuery object is in memory. http://api.jquery.com/category/traversing/tree-traversal/ – Esailija Aug 13 '12 at 08:18
  • Thought about mentioning that, too (hence my suggestion to remove the element sets off by retrieving the DOM reference from the jQuery object). I didn't go so far as to mention code like `(function globalFunc (elem){return elem.value;})(document.getElementById('foo'));` because I've gotten a lot of comments on that being _overly far-fetched_ or something, but I linked to a question on closures and Mem-leaks instead – Elias Van Ootegem Aug 13 '12 at 08:29
16

Is the stuff under “Detached DOM Tree” just waiting to be garbage collected or are these real leaks?

Before taking snapshot the browser will run garbage collection and sweep all objects that are not referenced. So the heap snapshot always contain only live objects. As a consequence if a Detached DOM Tree is in the snapshot than there must be an element in the tree that is referenced from JavaScript.

Also does anyone know how to find out what function is holding on to a reference to a detached element?

There should be an element(or several of them) in the same detached DOM tree that has yellow background. Such elements are referenced from the JavaScript code. You can find out who exactly keeps reference to the element in the retainers tree.

Yury Semikhatsky
  • 2,144
  • 13
  • 12
  • 1
    The second part of your answer was very helpful. Thank you. Do you know what the red background means? – AndrewHenderson Feb 22 '13 at 19:30
  • 9
    The red ones are the DOM nodes that are retained exclusively by the DOM tree and there are no direct references to them from javascript code. This means in particular that it is impossible that all elements in a detached dom tree are red as the tree would be collected in that case. There is always at least one node with either a yellow or white background. – Yury Semikhatsky Feb 27 '13 at 06:51
  • @YurySemikhatsky is there an easy way to find the yellow background ones? – Orlando Feb 14 '14 at 18:30
  • @YurySemikhatsky Thanks no one was explaining what the red means. Now that you say it my memleak has a pattern :) – Dominic Jul 31 '14 at 14:24
1

Since you've added the jQuery tag, I had a sneaky suspicion as to this being a jQuery thing. A quick google brought me to this page. When using jQ's detach method, a reference to the object is still kept in memory, so that could be causing your snapshot.

Another thing could be that jQuery has a div node at hand, which is -obviously- kept in memory, but never added to the actual dom... a sort of document.createNode('div') without appending it. This, too, will show up in the memory snapshot. You can't get around this, jQuery uses it to parse strings into html elements.

So to remove some elements from memory, use the jQuery .remove() method, and your mem will be cleared instantly.cf Esailija's comment on why remove doesn't quite fit the bill here.
$('#someElem')[0].parentNode.removeChild($('#someElem')[0]); Should remove the element altogether, but might not unbind the events. Perhaps something along the lines of:

$('#someElem').detach();//to remove any event listeners
$('#someElem')[0].parentNode.removeChild($('#someElem')[0]);//remove element all together

And, again, as Esailija pointed out in his answer, make sure that you assign references to any jQuery object (var someRef= $('.someSelector');) to a global variable, as they won't be GC'ed. Just avoid globals all together, in fact.
But to answer your question briefly, and clearly: no these aren't real memory leaks, the memory should be freed on the onbeforeunload event. The jQuery object is deleted, so all references go out of scope. At least, That's what my "research" lead me to believe. Perhaps not entirely relevant, but just as a reference Here's a question on mem-leaks I posted a while back, and with it a few things I found out..

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • jQuery `.remove()` is not any different from `.detach()` regarding this. The difference is that `.remove()` clears data from `jQuery.cache` (which does not reference any element unless it's by user code) for the elements. – Esailija Aug 13 '12 at 08:11
  • Thanks, it's been a while since I've used jQuery. I based that part of my answer on the article I linked too – Elias Van Ootegem Aug 13 '12 at 08:13
  • Note that those dom removal methods (`.removeChild etc, jQuery's .remove, .detach etc`) only disconnect the element from dom. This means clearing some references for it because its dom tree siblings, parents and such no longer reference to it, but it doesn't mean all references are cleared. For example, any element in the document is a global because for example `window.document.body.firstChild.nextSibling.nextSibling.firstChild` is a direct reference to it. Removing it from the main dom tree just removes these references but not necessarily all. – Esailija Aug 13 '12 at 08:27
  • True, but in this context it should suffice. We've both pointed out that he should make sure the all referencing variables are eligible for Garbage Collection, hinted at the risks of closures in that respect etc... – Elias Van Ootegem Aug 13 '12 at 08:33
  • The elements not being in the main DOM tree is a given here because it shows up in the `Detached DOM tree`-category. So my point is that any dom removal method is ineffective here because they are not attached to begin with. – Esailija Aug 13 '12 at 08:36
  • I see where you're comming from, but that's hardly a given here, IMO. The screenshots show a memory snapshot - No telling what code has been executed, what event handlers have been called etc... since the op is checking his code, it's very likely it's not a snapshot of a clean state, freshly loaded page. Anyhow, even if it were: jQuery does keep a detached div in its internals, which will always show up as a detached node, nothing the OP can do about that – Elias Van Ootegem Aug 13 '12 at 08:44
  • I mean, this question is concerned about Detached DOM trees. So it's a given that these elements are not in the dom tree, so calling remove or the like on them will not do anything. And jQuery does keep document fragment cache around, which looks like is the "problem" here. – Esailija Aug 13 '12 at 08:50
  • Look, I understand your point, and yes, the DOM caching is very likely (part of) the issue at hand. My point was that, perhaps some of the OP's code created (global) references to certain elements, only to remove them further down the code - in which case they might also show up as detached elements. Alongside the detached div jQuery relies on. The OP wanted to know if this is a mem-leak - to which the answer is: not really, it leaks until the page unloads or is refreshed. Weather it's the caching or assigned references that _outlive_ the element – Elias Van Ootegem Aug 13 '12 at 09:04