identifying roots using the heap inspector

2,584 views
Skip to first unread message

Godmar Back

unread,
Jun 21, 2013, 8:14:47 AM6/21/13
to google-chrome-...@googlegroups.com

Hi,

I have a basic question about how to use the Chrome heap profiler to find the roots that keep objects alive. I checked the available documentation & tutorials but didn't find an answer to my question.

Is it always possible to identify what's keeping an object alive using the heap inspector display?  For example, in the attached snapshot, how can I tell what's keeping DOMWindow @673633 alive (it's the selected object in the screenshot).

I had thought that the "Object's retaining tree" would provide that information - displaying the (reverse) tree from the object to all its roots. However, the displayed paths end in nodes such as "window in function() @677239" which are not roots - or at least, I cannot tell why "function() @677239"'s closure is still alive. Should the heap snapshot provide this information and if so, where?

Can someone comment/shine a light on the general technique used to determine why an object is alive in a Chrome heapdump?

Thanks.

 - Godmar

understandingcontainment.png

jaredwilli

unread,
Jun 22, 2013, 2:16:21 PM6/22/13
to google-chrome-...@googlegroups.com
Not sure if it will help, but something that I learned which helped me figure out what was keeping things alive and not garbage collected was hovering over the node in question and you should see a tooltip popup with the objects it contains.

However, the more valuable use of the heap profiler, at least in my experience, is for finding memory leaks. Tracking things like that down is possible by taking a heap snapshot, then performing some kind of action which you think may cause a leak an odd number of times (like 13), then taking another snapshot and doing a comparison of the two. 

You then find the Detached Dom trees and determine what is keeping those alive as the things with red backgrounds are things that take up memory since the references to them are kept but they are no longer used in any way. I've found this to be a huge problem on some apps.

Finding the cause of that I believe is far more important than finding what is keeping something still used alive. I can see why you would do that though too, just not as much of a priority I guess.

Ilya Tikhonovsky

unread,
Jun 22, 2013, 2:34:58 PM6/22/13
to Godmar Back, Google Chrome Developer Tools
The picture you captured looks strange. Could you please enable setting "Show objects' hidden properties" in the settings panel.
And the second question what version of chrome are you use.

Regards,
Tim.



 - Godmar

--
You received this message because you are subscribed to the Google Groups "Google Chrome Developer Tools" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-chrome-develo...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Godmar Back

unread,
Jun 22, 2013, 3:17:11 PM6/22/13
to Ilya Tikhonovsky, Google Chrome Developer Tools

See attached screenshot.

This is with Chrome stable: Version 28.0.1500.52 m
The snapshot was created in node.js; not sure which version of v8 that was using.

 - Godmar

retainingtreewithhiddenprops.png

jaredwilli

unread,
Jun 22, 2013, 8:40:34 PM6/22/13
to google-chrome-...@googlegroups.com, Godmar Back
Is there something I'm missing that "Show objects' hidden properties" does which I'm not aware of? See https://code.google.com/p/chromium/issues/detail?id=246840. I'd love to know if it does actually do something if in fact it does.

Sorry if this posts twice, it appeared to not post the first time a couple hours ago.

Regards,
Tim.


To unsubscribe from this group and stop receiving emails from it, send an email to google-chrome-developer-tools+unsub...@googlegroups.com.

Yury Semikhatsky

unread,
Jun 24, 2013, 5:09:16 AM6/24/13
to jaredwilli, Google Chrome Developer Tools, Godmar Back
On Sun, Jun 23, 2013 at 4:40 AM, jaredwilli <jared...@gmail.com> wrote:
> Is there something I'm missing that "Show objects' hidden properties" does
> which I'm not aware of? See
> https://code.google.com/p/chromium/issues/detail?id=246840. I'd love to know
> if it does actually do something if in fact it does.
>
Many parts of V8 engine are implemented in JS. Also there are many
data structures accessed solely from C++ code of V8 which nevertheless
live in the same heap as JS objects. It allows V8 to leverage
automatic garbage collection for the internal objects. By design user
application code cannot access those objects and cannot affect them
directly. Also the internal properties should never cause memory leaks
in the application's code. All this means that the users should not
worry about those internal references/objects. This is in theory. In
practice V8 has quite complex optimizations which might cause memory
leaks like the one described in
https://code.google.com/p/v8/issues/detail?id=2683. In such cases it
might be useful to take a look at the hidden properties as well. But
again unless you are working on V8 you shouldn't worry about those
links.
>>> email to google-chrome-develo...@googlegroups.com.
>>> For more options, visit https://groups.google.com/groups/opt_out.
>>>
>>>
>>
>>
> --
> You received this message because you are subscribed to the Google Groups
> "Google Chrome Developer Tools" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to google-chrome-develo...@googlegroups.com.

Yury Semikhatsky

unread,
Jun 24, 2013, 5:20:21 AM6/24/13
to Godmar Back, Google Chrome Developer Tools
If we are talking about Page heap snapshots then Window instances
should be considered a root objects that is kept alive by the browser
code. All of the alive objects in your application should have a
retaining path starting at a Window object. There are some nuances,
e.g. some objects may be held by debugger code but in general you
should be looking for references starting at global objects which are
Windows.

On Sat, Jun 22, 2013 at 11:17 PM, Godmar Back <god...@gmail.com> wrote:
>
> See attached screenshot.
>
> This is with Chrome stable: Version 28.0.1500.52 m
> The snapshot was created in node.js; not sure which version of v8 that was
> using.

I'm not familiar enough with node.js code to tell what should be
considered as global objects there. Looking at the attached
screenshot, I'd assume that there is a DOMWindow instance which is a
global object for a given context. Could you provide more details on
how you took the snapshot and how we could reproduce this?


On Fri, Jun 21, 2013 at 4:14 PM, Godmar Back <god...@gmail.com> wrote:
>

Godmar Back

unread,
Jun 24, 2013, 7:44:44 AM6/24/13
to Yury Semikhatsky, Google Chrome Developer Tools
On Mon, Jun 24, 2013 at 5:20 AM, Yury Semikhatsky <yu...@chromium.org> wrote:
>
> This is with Chrome stable: Version 28.0.1500.52 m
> The snapshot was created in node.js; not sure which version of v8 that was
> using.

I'm not familiar enough with node.js code to tell what should be
considered as global objects there. Looking at the attached
screenshot, I'd assume that there is a DOMWindow instance which is a
global object for a given context. Could you provide more details on
how you took the snapshot and how we could reproduce this?


The 'DOMWindow' objects in the heap snapshot are pure JS, they're implemented by jsdom. During the execution of an application, they become a global object via Contextify https://npmjs.org/package/contextify The snapshot, however, was taken via OFE at a point in time when we've disposed of those contextified objects (at least that's what we think).

Let me ask you this: does the heap dump  file contain the information that would allow me to tell whether DOMWindow is a root?  If so, I could write my own program to process it. In that case, there would be a problem with our code that unroots/unpins the window object. I would then also suggest that Chrome's heap inspectore visualizes this information in some way.  Also, in that case, shouldn't the object appear at the top-level in the 'containment pane'? As you can see from the attached snapshot, it does not.

 - Godmar

containmentpane.png

Yury Semikhatsky

unread,
Jun 24, 2013, 11:27:50 AM6/24/13
to Godmar Back, Google Chrome Developer Tools
Strictly speaking, the only real root in the containment view is (GC
roots). But DevTools front-end also shows global objects of all
contexts at the top level. This presentation is based on the
assumption (which is true for Chromium) that there are strong
references to the contexts from C++ code. So the other 3 objects on
the list should be root objects. But again I don't know how
contextification works and whether it creates a strong reference from
the native part to the new contexts or the contexts are just held by
the functions created in them in which case the assumption might not
be held for the node.js code.



> - Godmar
>

Alexei Filippov

unread,
Jun 24, 2013, 11:29:14 AM6/24/13
to Godmar Back, Ilya Tikhonovsky, Google Chrome Developer Tools
Hi Godmar,

With enabled system properties I can see that the window property is held by an accessor closure (getter or setter) of location property in HTMLDocument.
However in normal flow there should be named closure shortcuts get-location and set-location present in the snapshot. Once they are there the path to the global roots should be visible even if "show objects' hidden properties" option is off.

It is very interesting to track down why these shortcuts are not created. Could you please share the snapshot file?

Thanks,
Alexei


2013/6/22 Godmar Back <god...@gmail.com>

Godmar Back

unread,
Jun 24, 2013, 12:09:45 PM6/24/13
to Alexei Filippov, Ilya Tikhonovsky, Google Chrome Developer Tools
On Mon, Jun 24, 2013 at 11:29 AM, Alexei Filippov <al...@google.com> wrote:
Hi Godmar,

With enabled system properties I can see that the window property is held by an accessor closure (getter or setter) of location property in HTMLDocument.
However in normal flow there should be named closure shortcuts get-location and set-location present in the snapshot. Once they are there the path to the global roots should be visible even if "show objects' hidden properties" option is off.

It is very interesting to track down why these shortcuts are not created. Could you please share the snapshot file?


Godmar Back

unread,
Jun 24, 2013, 12:51:31 PM6/24/13
to Yury Semikhatsky, Google Chrome Developer Tools

If there is a strong reference from C++ to an object, how would this information be represented in the heap dump file?
Does it say: "this is pinned by native code", or is it the absence of an incident edge that would allow this conclusion?
And if it can be determined that an object is kept alive from C++, does the heap inspector visualize this information?

It is possible that in our environment some of the assumptions under which the heap inspector was developed do not hold. I'm just trying to understand to what extent my lack of understanding is caused by the heap inspector not displaying something vs. v8 not dumping the necessary information, vs. possible v8 internal bugs, vs. errors in my application code (a strong ref from js code, or failure to unpin by our C++ code).  To that end, I was hoping to track the object that fails to get collected to its roots.

On that node, I've attached another screenshot where I discovered a possible path. Here I started with the Containment view and drilled down one of those objects (@650507). After expanding a few levels deep I came across builtins @674015, which also appears on the retaining tree of my suspect object @673633. In other words, there's a path @650507 -> @674015 -> @673633, even though the heap inspector doesn't easily reveal it. You can approach the path in the containment pane top down until you reach @674015, and you can approach the path bottom up in the retaining tree window until you each @674015, but you don't see the entire path.

Is there an explanation as to why the path is split in this way?  Is there any significance to the object 'builtin @674015' that would justify that?
Is my conclusion that @650507 is really what's keeping @673633 alive correct?  

 - Godmar

rootpathquestion.png

Alexei Filippov

unread,
Jun 25, 2013, 2:06:35 PM6/25/13
to Godmar Back, Yury Semikhatsky, Google Chrome Developer Tools
Hi Godmar,
Please find the answers inline.

2013/6/24 Godmar Back <god...@gmail.com>


If there is a strong reference from C++ to an object, how would this information be represented in the heap dump file?

It should be listed in one of the categories under (GC Roots) placeholder. 

Does it say: "this is pinned by native code", or is it the absence of an incident edge that would allow this conclusion?

Once you have opened the retainment path down to an object having distance 2, you reached an object that is held by native code. Well, should reach.
Such an object should have been listed in (GC Roots).
Yes, the distance of 2 seems very strange. This patch https://src.chromium.org/viewvc/blink?view=revision&revision=153009 brings it down to 0.

And if it can be determined that an object is kept alive from C++, does the heap inspector visualize this information?

The distance of 2 (0 in upcoming version) should be an indication.

It is possible that in our environment some of the assumptions under which the heap inspector was developed do not hold. I'm just trying to understand to what extent my lack of understanding is caused by the heap inspector not displaying something vs. v8 not dumping the necessary information, vs. possible v8 internal bugs, vs. errors in my application code (a strong ref from js code, or failure to unpin by our C++ code).  To that end, I was hoping to track the object that fails to get collected to its roots.

On that node, I've attached another screenshot where I discovered a possible path. Here I started with the Containment view and drilled down one of those objects (@650507). After expanding a few levels deep I came across builtins @674015, which also appears on the retaining tree of my suspect object @673633. In other words, there's a path @650507 -> @674015 -> @673633, even though the heap inspector doesn't easily reveal it. You can approach the path in the containment pane top down until you reach @674015, and you can approach the path bottom up in the retaining tree window until you each @674015, but you don't see the entire path.

Is there an explanation as to why the path is split in this way?  Is there any significance to the object 'builtin @674015' that would justify that?
Is my conclusion that @650507 is really what's keeping @673633 alive correct?  


The problem is in how heap snapshotter in v8 defines embedder root objects it places on top level.
Currently it looks up for JSGlobalProxy in entire heap and consider them as global [window] objects.
In your case it happened to be DOMWindow @650507, DOMWindow @36553, and @697.
Then the distance for rest of the heap objects is calculated starting from these global objects.
That logic works pretty well for Blink, but seems to break for other embedders.

We're considering changing the algorithm of defining global objects. It should probably not treat an object as global if nobody holding a native handle on it.

Cheers,
Alexei

P.S. as a small bonus I've managed to track the real retainment path for the DOMWindow @650507 (please see the attached screenshot).

 - Godmar

DOMWindow-650507-to-root-path.png

Godmar Back

unread,
Jun 26, 2013, 4:04:43 PM6/26/13
to Alexei Filippov, Yury Semikhatsky, Google Chrome Developer Tools
On Tue, Jun 25, 2013 at 2:06 PM, Alexei Filippov <al...@google.com> wrote:

It is possible that in our environment some of the assumptions under which the heap inspector was developed do not hold. I'm just trying to understand to what extent my lack of understanding is caused by the heap inspector not displaying something vs. v8 not dumping the necessary information, vs. possible v8 internal bugs, vs. errors in my application code (a strong ref from js code, or failure to unpin by our C++ code).  To that end, I was hoping to track the object that fails to get collected to its roots.

On that node, I've attached another screenshot where I discovered a possible path. Here I started with the Containment view and drilled down one of those objects (@650507). After expanding a few levels deep I came across builtins @674015, which also appears on the retaining tree of my suspect object @673633. In other words, there's a path @650507 -> @674015 -> @673633, even though the heap inspector doesn't easily reveal it. You can approach the path in the containment pane top down until you reach @674015, and you can approach the path bottom up in the retaining tree window until you each @674015, but you don't see the entire path.

Is there an explanation as to why the path is split in this way?  Is there any significance to the object 'builtin @674015' that would justify that?
Is my conclusion that @650507 is really what's keeping @673633 alive correct?  


The problem is in how heap snapshotter in v8 defines embedder root objects it places on top level.
Currently it looks up for JSGlobalProxy in entire heap and consider them as global [window] objects.
In your case it happened to be DOMWindow @650507, DOMWindow @36553, and @697.

Before I dig deeper into v8's internal, could you briefly describe what a JSGlobalProxy does and how it is created.
In looking at Contextify, I do not immediately see any uses of it. I'm guessing that it represents a global scope, something a browser would create for every window?

Then the distance for rest of the heap objects is calculated starting from these global objects.
That logic works pretty well for Blink, but seems to break for other embedders.

We're considering changing the algorithm of defining global objects. It should probably not treat an object as global if nobody holding a native handle on it.

So, that means that the heap snapshot has information about what's being held by native code?  BTW, when we say "held by native code", what do we mean specifically?  Objects referred to using a "Persistent<>" handle, along with objects referred to via Local handles whose HandleScopes are still active?
 

Cheers,
Alexei

P.S. as a small bonus I've managed to track the real retainment path for the DOMWindow @650507 (please see the attached screenshot).

I'm actually interested in DOMWindow  @673633.

But, let me ask another question. When it says in the snapshot "[1192] in (Global handles) @27", does that means that there's a 'Persistent<>' handle created by native code?  Or what else is the 'Global handles' array?

Thanks!

 - Godmar

Godmar Back

unread,
Jun 26, 2013, 4:23:02 PM6/26/13
to Alexei Filippov, Yury Semikhatsky, Google Chrome Developer Tools
On Tue, Jun 25, 2013 at 2:06 PM, Alexei Filippov <al...@google.com> wrote:

P.S. as a small bonus I've managed to track the real retainment path for the DOMWindow @650507 (please see the attached screenshot).


As I mentioned, I'm interested in DOMWindow @673633, but, just out of curiosity, how would I interpret the root path for DOMWindow @650507 shown in your screenshot?

At the end of the day, I need to find the strong JS references that keep an object alive. In your snapshot, the chain contains gray entries such as '1 in function () @174215', '4 in (code) @174221', '2 in @749672', '30 in (map descriptors)' etc. with are visible only when I turned on 'hidden properties'. As a matter of fact, without turning this on, I don't see any path from @650507 to [9] in (GC Roots).

Our code is 99.5% JavaScript, with the exception of using 3 Persistent<> handles in Contextify. My hope is that we should be able to debug retention issues hopefully without having to dig too deep into v8's internal representations.

Perhaps this is a concern to other users too (?).

Thanks for any help, and I hope you find this feedback useful.

 - Godmar

Yury Semikhatsky

unread,
Jun 28, 2013, 2:47:40 AM6/28/13
to Godmar Back, Google Chrome Developer Tools


On Mon, Jun 24, 2013 at 8:51 PM, Godmar Back <god...@gmail.com> wrote:
>
> If there is a strong reference from C++ to an object, how would this
> information be represented in the heap dump file?
> Does it say: "this is pinned by native code", or is it the absence of an
> incident edge that would allow this conclusion?
> And if it can be determined that an object is kept alive from C++, does the
> heap inspector visualize this information?
>
The only possible way to keep a reference to an object from C++ code using V8 public API is to create a Handle<>: either Persistent or Local (the latter will be destroyed when its v8::HandleScope object is destroyed). This means that if an object is retained by the native code there must be at least one Handle pointing to that object. In the heap snapshot you sent us DOMWindow@174035 is retained by a Global Handle (see attached screenshot) so I'd assume that there is a persistent handle in the native code that holds the reference and keeps the object alive.

There are quite a few DOMWindow objects in the heap and some of them might be parts of a single global object, the fact that there are several parts may be undetectable from JS code but in heap snapshot they should be present as separate entities. E.g. in Chromium window global object has quite complicated structure.

Bear in mind that (Global Handles) contains both persistent handles created using V8 public API and those created by V8 internally.
DOMWindow retainer.png

Yury Semikhatsky

unread,
Jun 28, 2013, 5:25:28 AM6/28/13
to Godmar Back, Alexei Filippov, Google Chrome Developer Tools
On Thu, Jun 27, 2013 at 12:04 AM, Godmar Back <god...@gmail.com> wrote:
On Tue, Jun 25, 2013 at 2:06 PM, Alexei Filippov <al...@google.com> wrote:

It is possible that in our environment some of the assumptions under which the heap inspector was developed do not hold. I'm just trying to understand to what extent my lack of understanding is caused by the heap inspector not displaying something vs. v8 not dumping the necessary information, vs. possible v8 internal bugs, vs. errors in my application code (a strong ref from js code, or failure to unpin by our C++ code).  To that end, I was hoping to track the object that fails to get collected to its roots.

On that node, I've attached another screenshot where I discovered a possible path. Here I started with the Containment view and drilled down one of those objects (@650507). After expanding a few levels deep I came across builtins @674015, which also appears on the retaining tree of my suspect object @673633. In other words, there's a path @650507 -> @674015 -> @673633, even though the heap inspector doesn't easily reveal it. You can approach the path in the containment pane top down until you reach @674015, and you can approach the path bottom up in the retaining tree window until you each @674015, but you don't see the entire path.

Is there an explanation as to why the path is split in this way?  Is there any significance to the object 'builtin @674015' that would justify that?
Is my conclusion that @650507 is really what's keeping @673633 alive correct?  


The problem is in how heap snapshotter in v8 defines embedder root objects it places on top level.
Currently it looks up for JSGlobalProxy in entire heap and consider them as global [window] objects.
In your case it happened to be DOMWindow @650507, DOMWindow @36553, and @697.

Before I dig deeper into v8's internal, could you briefly describe what a JSGlobalProxy does and how it is created.
In looking at Contextify, I do not immediately see any uses of it. I'm guessing that it represents a global scope, something a browser would create for every window?
JSGlobalProxy is the |globlal_object| argument that was passed to v8::Context::New or the one created by V8 if none was specified. It is basically used to preserve global object identity on frame navigation if we are talking about browsers (see comment for v8::Context::Global()). As its name implies it is a proxy object and any attempt to set a property on the global object from JS will result in adding the property to the context's inner global object.

In case of Chromium the retainment chain for a frame global object is the following:

v8::Persistent<v8::Context> --> "system / NativeContext" --'global'--> Window@1 --'global_receiver'--> Window@2

Window@1 is the actual holder of all properties set on the window global object and we measure distances starting from this object. It should be GC'ed after frame navigation. This is the object users should worry about.
Window@2 is the JSGlobalProxy object which is preserved on navigation and should not keep anything meaningful per see.


I've noticed that in your snapshot "system / NativeContext" is called "system / GlobalContext". They were renamed almost a year ago and I would definitely recommend trying a newer version of V8.

 

Then the distance for rest of the heap objects is calculated starting from these global objects.
That logic works pretty well for Blink, but seems to break for other embedders.

We're considering changing the algorithm of defining global objects. It should probably not treat an object as global if nobody holding a native handle on it.

So, that means that the heap snapshot has information about what's being held by native code? BTW, when we say "held by native code", what do we mean specifically?  Objects referred to using a "Persistent<>" handle, along with objects referred to via Local handles whose HandleScopes are still active?
 
Correct. As I said the only way retain an object from native code is to create Local or Persistent handle. All handles can be found under (Global handles) and (Handle scope) entries.
 

Cheers,
Alexei

P.S. as a small bonus I've managed to track the real retainment path for the DOMWindow @650507 (please see the attached screenshot).

I'm actually interested in DOMWindow  @673633.

But, let me ask another question. When it says in the snapshot "[1192] in (Global handles) @27", does that means that there's a 'Persistent<>' handle created by native code?
Yes. This is true.
 
 Or what else is the 'Global handles' array?

V8 alsow uses persistent handles internally. Public and internal handles are basically 
indistinguishable and we show both types under (Global Handles) node.
Reply all
Reply to author
Forward
0 new messages