最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Locating detached DOM tree memory leak - Stack Overflow

programmeradmin0浏览0评论

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!

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!

Share Improve this question asked Jan 20, 2014 at 5:10 RafeRafe 5,2953 gold badges24 silver badges26 bronze badges 12
  • Can you share some of the code, specifically where this element is created and where it is disposed? – Etai Commented May 1, 2014 at 8:13
  • I'm afraid the code is proprietary and enormous; I haven't had a chance to try reproducing it in the small. The real issue here is why the Chrome heap profiler should ever report something like the above which doesn't bottom out at the root (how can this be?!). – Rafe Commented May 2, 2014 at 0:36
  • 1 You have to remove the property foobar(with delete; delete button.foobar before you remove the DOM element and make sure that there are no remaining event listeners. – GuyT Commented Jun 3, 2014 at 12:44
  • 1 I don't think it's a bug. At the moment I'm developing a huge SPA with my own framework combined with DOJO. Memory leaks where also a big issue. The most memory leaks where created because I didn't appropriate destroy the connections(event listeners) and I was referencing to other objects(outer scope). After changing my 'normal' objects to the module pattern my leaks where gone. Summarizing; Could you post the code how you create the button with the foobar property? – GuyT Commented Jun 4, 2014 at 7:07
  • 1 @Rafe But how are we then supposed to help you? The solution is already given: you still have a reference to the object. The GC is counting the remaining references to an object. If it becomes 0 the object will be garbage collected. Otherwise, when it is possible that an object could be called again there will be always a reference. There are many kinds of memory leaks: in closures, circular reference, .. If no code is provided we only can explain you the theory. In earlier days, I've tried to solve memory leaks in a huge app(over 1GB JS), – GuyT Commented Jun 5, 2014 at 6:30
 |  Show 7 more comments

1 Answer 1

Reset to default 15

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

发布评论

评论列表(0)

  1. 暂无评论