I'm having trouble diagnosing a detached DOM tree memory leak in a very large single-page web app built primarily with Knockout.
I've tweaked the app to attach a dummy FooBar
object to a particular HTML button element which should be garbage collected as the user moves to a different "page" of the app. Using Chrome's heap snapshot function, I can see that an old FooBar
instance (which should have been GC'ed) is still reachable from its HTMLButtonElement
in a (large) detached DOM tree.
Tracing the references via the retaining tree panel, I follow the chain taking decreasing distance from the GC root. However, at some point my search reaches a dead end at a node distance 4 from the root (in this case)! The retaining tree reports no references to this node at all, yet somehow knows it is four steps from the GC root.
Here is the part of the retaining tree which has me puzzled (the numbers on the right are distances from the root):
v foobar in HTMLButtonElement 10 v [4928] in Detached DOM tree / 5643 entries 9 v native in HTMLOptionElement 8 v [0] in Array 7 v mappedNodes 6 v [870] in Array 5 v itemsToProcess in system / Context 4 context in function itemMovedOrRetained() context in function callCallback()
The retaining tree doesn't show the references here at distance 3 or above.
Can anyone explain this to me? I was hoping I'd be able to follow the reference chain back up to the offending part of the JavaScript app code -- but this has my stymied!
Memory leaks are a common error in programming, especially when using languages that have no built in automatic garbage collection, such as C and C++. Typically, a memory leak occurs because dynamically allocated memory has become unreachable.
Detached DOM elements are the elements which have been removed from the DOM but their memory is still retained because of JavaScript. This means that as long the element have a reference to any variable or an object anywhere, it does not garbage collected even after destroyed from the DOM.
A DOM node can only be garbage collected when there are no references to it from either the page's DOM tree or JavaScript code. A node is said to be "detached" when it's removed from the DOM tree but some JavaScript still references it. Detached DOM nodes are a common cause of memory leaks.
First of all - do not use delete
as one of the comments suggested. Setting a reference to null
is the right way to dispose of things. delete
breaks the "hidden class". To see it yourself, run my examples from https://github.com/naugtur/js-memory-demo
Rafe, the content you see in profiler is often hard to understand. The bit you posted here does seem odd and might be a bug or a memory leak outside of your application (browsers leak too), but without running your app it's hard to tell. Your retaining tree ends in a context of a function and it can be retained by a reference to that function or some other function sharing the context. It might be too complicated for the profiler to visualize it correctly.
I can help you pinpoint the problem though.
First, go to Timeline tab in devtools and use it to observe the moment your leak happens. Select only memory allocation and start recording. Go through a scenario that you expect to leak. The bars that remain blue are the leaks. You can select their surrounding in the timeline and focus on their retaining tree. The most interesting elements in detached dom trees are the red ones - they're referenced from the outside. The rest is retained because whatever element in a tree is referenced, it has references to everything else (x.parentNode
)
If you need more details, you can take multiple snapshots in the profiler, so that you have a snapshot before and after the cause of the leak (that you found with the timeline - you now know the exact action that causes it). You can then compare those in the profiler - there's a "compare" view. which is more comprehensible than others.
You can also save your heap snapshots from the profiler and post them online, so we could take a look. There's a save link on each of them in the list to the left.
Profiling memory is hard and actually requires some practice and understanding of the tools. You can practice on some examples from my talk:
http://naugtur.pl/pres/mem.html#/5/2
but the real complete guide to using memory profiler is this doc:
https://developer.chrome.com/devtools/docs/javascript-memory-profiling#looking_up_color_coding
Updated link: https://developers.google.com/web/tools/profile-performance/memory-problems/memory-diagnosis
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With