(with thanks to Mikhail Naganov for his feedback on the Developer Tools mailing list)

Chrome’s Developer Tools contain some useful features for inspecting memory usage of a given page (and its change over time), but the documentation for these features is a bit sparse—and, if you are unfamiliar with these sorts of tools and what they do, their output can seem undecipherable. Hopefully this brief post helps explain these features and what they can do for you.

The Memory Timeline

Screenshot of Chrome’s Memory Timeline feature

The memory timeline gives you an overview of memory usage over time. This makes it very easy to see how much memory various parts of your application use and can provide a strong visual cue if your application is leaking memory over time. The blue area represents the amount of memory in use by your app at a given time; the remaining white area represents the total amount of allocated memory. The number in the top-left corner (as of Chrome 9) indicates the total amount of allocated (available) memory, not the total amount of used memory. In nearly all cases, your concern lies only with the amount of used memory.

You can see exactly how much memory was in use at the end of a particular event by hovering over the name of the record in the list of records until a bubble appears. Records are also recorded any time V8’s garbage collector runs, telling us how much memory was reclaimed. It is important to note that V8’s garbage collector is incredibly complex and may take up to 5 runs before it garbage collects an unused object, so don’t assume that everything that can be collected has been collected when you see that GC has occurred.

The Heap Profiler

Screenshot of Chrome’s Heap Profiler feature

The heap profiler is a somewhat more complicated tool than the memory timeline: while the memory timeline shows you how much memory is in use over time, the heap profiler gives you an overview of all of the objects in memory at the moment the snapshot was taken. This allows you to drill down and see exactly what kinds of objects are responsible for using memory at a given point in time.

Before going over the main table of the heap snapshot, I’ll briefly explain the difference between “code” and “objects” in the two pills at the bottom of the window.

Code objects are bits of JIT-compiled code that get stored in memory by the JavaScript engine. There are three principal types of code objects: Scripts, which are objects that contain functionless code executed directly within a <script> tag (e.g. <script>var foo = 42;</script>); SFIs, which are objects that contain the actual code for a function; and Functions, which are essentially wrappers that contain a pointer to an SFI (for the code) and information about the function’s lexical scope (which, when combined, form the basis of a complete function call).

Compiled code objects are separated from all other Objects, which are non-executable data stored in memory—Object, Arrays, Strings, and so on. (Uncompiled script source code gets stored here as well, as String data.) This separation is mostly irrelevant to JavaScript developers as there is no real concept of executable vs non-executable memory in JavaScript itself, but it is important to know that “code” here refers to JIT-compiled code, not source code.

In Chrome 9, the minimum number of code and object references in any page is around 5500 (about 580kB of used heap space). This is space that is taken up by native objects that you’d expect to see in any ECMAScript environment—RegExp, Date, Math, and so on. Access the window object in your code and the reference count jumps up to around 9000 (815kB). These numbers can be used as a rough baseline for the minimum amount of data that can exist on a page.

The table of data that the heap profiler provides can seem bewildering at first. Unlike profiling CPU time where you get a list of results by method name adding up to 100% time spent, the heap snapshot is an infinitely recursive list of objects in memory grouped by object constructor. Each group has a count that shows you how many references to objects of that type existed at the time the snapshot was taken. By creating multiple snapshots, you can profile the behaviour of your application at specific points in time to see which types of objects are being created, retained, and destroyed in response to certain events (like switching between views).

Another feature of the heap profiler is the ability to drill down into groups to see which other groups of objects are holding references to a given group of objects. There are two important things to know about this view:

  1. Parent groups expand to show references from the children to the parent group, not references to the children from the parent group. (Consider it a drill-up, rather than a drill-down, though really when it comes to memory, it’s all pretty cyclical.)
  2. The counts illustrate the number of references between objects, not the number of objects themselves. One child object can have many references to the same parent object, either directly or through other references, and there is no way to tell how many objects are responsible for all of the references within a given group.

You will also see a few special object types that are important to understand:

“(global property)” is a special intermediate object that stands between the global object (in the browser, this is window) and any objects that are referenced by the global object. This is done to improve performance, since the mechanism that is used to speed up property look-ups on regular objects does not work as well for property lookups on the global object.

The “(closure)” group simply indicates the number of references to the expanded group of objects through any closures. Closures are fantastically useful in JavaScript, but they can cause huge problems with unintentional memory retention (since V8’s current GC won’t clean up any of the memory from a closure until all the members of the closure have gone out of scope—Wikipedia tells me this type of garbage is called “semantic garbage”). Use them sparingly.

Finally, and most importantly, “(roots)” are the special group of objects that are used by the garbage collector as a starting point to determine which objects are eligible for garbage collection. A “root” is simply an object that the garbage collector assumes is reachable by default, which then has its references traced in order to find all other current objects that are reachable. Any object that is not reachable through any reference chain of any of the root objects is considered unreachable and will eventually be destroyed by the garbage collector. In V8, roots consist of objects in the current call stack (i.e. local variables and parameters of the currently executing function), active V8 handle scopes, global handles, and objects in the compilation cache. (Learn more about this topic by reading Mark Lam’s excellent article, Garbage Collection.)

Now that that’s out of the way, let’s look at a quick example. A relatively simple one to understand is the DOMWindow constructor, since there is typically only one DOMWindow on a given page—the window object—and that simplifies things a bit.

Screenshot of Chrome’s Heap Profiler feature illustrating recursion

When you expand the DOMWindow group, you’ll be able to see which groups of objects have references back to any of the objects in original group of DOMWindows. In this case, you can see that there are several instances where the same group of DOMWindows refer back to themselves. Recursive structures like these are incredibly common—the window object, for instance, has several properties that point back to itself: window, parent, self, top, and frames. There can often be circular references that occur between several different objects as well, such as window.document.defaultView (which points back to window). These infinitely recursive structures are accurately represented within the heap snapshot.

Caveats

Unfortunately, while still far beyond anything that other browser manufacturers currently provide to inspect memory, the heap profiler still has several huge issues that keep it from being an effective tool for debugging memory leaks—and, in my opinion, this is by far the most needed missing feature in today’s developer tools.

The first problem is that the Chrome/V8 garbage collector (as of Chrome 9) can take up to five rounds to find and clear unreferenced data. This means that even though data has fallen out of scope and is eligible for collection, it may still show up in the heap snapshot as if it were still in scope. This makes it very difficult to determine whether data is actually leaking because it is still being referenced somewhere, or whether the garbage collector simply hasn’t gotten around to cleaning it up yet.

The second problem is that there is no way to actually inspect individual objects in memory and learn exactly what they are, what they are referencing, where they were assigned, or their age—the heap profiler only shows you aggregate information about the number of references to a particular type of object. This means that if you use the same constructor in many different areas of an application, but they only leak in one place in your code, it becomes nearly impossible to actually determine which part of the application is responsible for the leak without checking every instance where a certain type of object is used. Being able to drill down to view individual objects and their retainers, and to see the age of each object, would be invaluable in determining where objects are leaking and why.

Caveats aside, Chrome is currently the only browser that provides any real useful level of memory inspection. As long-running, single-page apps become more and more prevalent, the need for features that allow web developers to inspect the memory usage of their applications continues to grow. Hopefully this explanation of how these tools work will encourage you to make them a part of your arsenal of web development tools—and, hopefully, some other browser manufacturers will start providing similar tools, since not all garbage collectors are created equal.

4 Responses to this post

  1. Great work. I think this article is one of its kind at the moment, which is of course sad, but it makes it even more valuable. Thanks. I really hope that Chrome guys will address the issues you brought up, especially second one. 'Cause at the moment all I can tell from Heap Profiler is that I indeed have memory leak (which I knew without it simply looking at how memory usage grows in task manager), but have no idea how to track the actual case down :|

  2. Nicely done. Came across it while trying to figure out what (code) referred to.

    For what it's worth, from what I'm seeing on Chrome 10, I see the heap snapshot updated immediately when I null out references (immediately as in, clear out references, and then take a snapshot) - so it seems like an accurate representation of what the heap would look like if GC had run.

  3. > "The first problem is that the Chrome/V8 garbage collector (as of Chrome 9) can take up to five rounds to find and clear unreferenced data."

    As of 12.0.722.0 (80146), there is a Garbage icon on the Timeline panel that will force a GC sweep.

  4. Thanks for posting; I've been trying to figure this out (using chrome stable, beta AND dev). Dev certainly is looking interesting, but the data on objects remains incomprehensible.

    What I'm hoping to find though (in case you have any insight on this) is how to get information about my objects to actually show up.

    Specifically, using the dojo framework, objects declared using traditional javascript show up in the heap profiler as "objectName" (i.e. "dojo.Deferred", while all objects declared using dojo show up as "a". Its really hard to trace the source of a problem when all objects are lumped together as "a"...