Memory leaks in JavaScript: how to get rid of them (2/2)

This article is the continuation of the article “the 4 types of memory leaks”
c

The unintuitive behavior of Garbage collectors

Although Garbage collectors are very practical, they come with their share of compromises. One of these compromises being their non-determinism. In other words, Garbage collectors are unpredictable. It is generally not possible to determine with certainty when a Garbage collection will be carried out. This implies that in some cases, the program uses more memory than necessary. In other cases, one may notice short interruptions in sensitive applications. Although non-deterministic means that one can never be sure when the collection will be performed, most GC implementations share a common pattern of performing the collection during allocation. When no allocation is made, most GS remain idle.
Consider the following scenario:

  1. A consequent set of allocations is made
  2. Most (or all) of these elements are marked as inaccessible (imagine nulling a reference that points to a cache that is no longer needed)
  3. No more allowances are made

In this scenario, most GCs will no longer collect. That is, even if there are inaccessible items available for collection, they are not claimed by the collector. These aren't leaks per se, but the result is higher than normal memory usage.
Google provides a great example of this behavior in their JavaScript Memory Profiling docs, example #2.

Introducing Chrome's Memory Profiling Tools

Chrome provides a good set of tools for diagnosing the memory usage of JavaScript code. There are mainly two views related to memory: the timeline view and the profile view.

Timeline view

The timeline view is essential for discovering unusual memory patterns in our code. When we are looking for a large leak, the periodic jumps that do not reduce as much as they have grown after collection should alarm you. In this capture, we see what the steady growth of a leaky object looks like. Even after the final big collection, the total amount of memory used is greater at the end than at the beginning. The number of Nodes too. So many hints of DOM leaks somewhere in the code.

Profile view

It is this view that you will spend a lot of time looking at. The profiles view allows you to get a snapshot and compare snapshots of the memory usage of your JavaScript code.
This allows you to save allocations over time. In all result views, there are different types of lists, but the most relevant for our task are the summary list and the comparison list.
The summary view gives us an overview of the different types of allocated objects and their aggregated size: shallow size (the sum of all objects of a specific type) and retained size (the shallow size plus the size of other objects retained because of this object). This also gives a notion of the distance of this object from its root GC (the distance).
The comparison list gives us the same information but allows us to compare the different snapshots. This is especially useful for finding leaks.
Example: Finding Leaks Using Chrome
There are mainly two types of leaks: leaks that cause periodic increases in memory usage, and leaks that happen once and do not cause further increases in memory usage. For obvious reasons, it is easier to find leaks when they are periodic. They are also the most problematic, if the memory increases over time, leaks of this type may slow down the browser or may cause the script to stop. Non-periodic leaks can be easy to find when they are large enough to be noticed among other allocations. This is usually not the case, so they go unnoticed. In a sense, leaks that happen just once could be categorized as optimization issues. That said, periodic leaks are bugs and should be fixed.
To illustrate, we take an example in the Chrome doc. The code is copied below:
var x = [];
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment();
for (;i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow,1000);
}
When grow is called, it will start by creating DIV nodes and attaching them to the DOM. It will also allocate a large array and add to it an array referenced by a global variable. This will cause a steady increase in memory which can be found using the tools mentioned above.
We generally observe an oscillating memory usage pattern in Garbage Collection-based languages. This is what is expected if the code runs on a loop producing the allocations, which is usually the case. We'll be looking for periodic memory boosts that don't fall back to previous levels after collection.
First, see if the memory increases periodically.
The View timeline is great for that. Open the example in Chrome, open the Dev Tools, go to the timeline, select memory and click the record button. Then go to the page and click on The Button to start the memory leak. Wait a bit then stop recording and watch the result:

Memory leak in timeline view

This example will continue to leak memory every second. After stopping recording, add a breakpoint in the grow function to prevent the script from forcing Chrome to close the page. There are 2 big clues in this image that show that we have leaking memory. The graphs of Nodes (green line) and JS heap (blue line). The knots increase steadily and never come down. This is a big red flag.
The JS Heap also shows a steady increase in memory usage. It's harder to see due to the effects of the Garbage Collector. You can observe a pattern of initial memory growth, followed by a large reduction, followed by growth, then a spike, followed by another reduction. The key in this case is that after each reduction in memory usage, the heap size remains larger than the previous time. This means that although the garbage collector successfully reclaims a lot of memory, some is regularly lost.
We are now certain that there is a leak. Let's find it.
d

Take two snapshots

To find the leak, we will now go to the profile section of Chrome Dev Tools. to keep memory usage to a manageable level, reload the page before this step. We are going to use the Take Heap Snapshot function.
Reload the page and take a Heap Snapshot right after it finishes loading. We will use this snapshot as a reference. Now click The Button again, wait a few seconds, and take a new snapshot. After taking the snapshot, it's a good idea to put a breakpoint in the script to prevent the leak from consuming more memory.

Heap Snapshots

There are two ways to look at allocations between two snapshots. Either you click on Summary and then go to the right and select Objects allocated between Snapshot 1 and Snapshot 2, or you click on Comparison instead of Summary. In both cases, we will see a list of objects that have been allocated between the two snapshots.
In this case, it is quite easy to find the leaks: they are large. Look at the Size Delta of the (string) constructor. 8MBs for 58 new items. This looks suspicious: new objects are allocated but not freed and 8MBs are consumed.
If we open the list of allocations for the (string) constructor we will notice that there are a few big allocations among lots of small ones. The big ones immediately grab our attention. If we select any of them, we will find something interesting with it in the Retainers section just below.

Retainers for the selected object

We see that the selected allocation is part of an array. In turn, the array is referenced by the variable x in the global window object. This gives us the full path from our big object to its non-collectable root (window). We found our potential leak and where it is referenced.
So far, so good. But our example was easy: big allocations like in the example are not the norm. Luckily, our example also leaks DOM nodes, which are smaller. It's easy to find these nodes using the snapshot above, but in larger sites things get more complicated. Recent versions of Chrome provide an additional tool that is well suited to our job: the Record Heap Allocations function.

Save heap allocation to find leaks

Disable the breakpoint you set before, let the script run, and return to the Profile section of Chrome Dev Tools. Now tap on Record Heap Allocations. As the tool runs, you will notice blue spikes in the top graph. it represents allowances. Every second, a big allocation is produced by the code. Let it run for a few seconds then stop it (don't forget to add a breakpoint to prevent Chrome from consuming more memory).
Heap saved allowances.
In this image, you can see the killer feature of this tool: select a portion of the timeline to see what allocations have been made during that time. We define the selection closest to the large peaks. Only three constructors appear in the list. One of them is the one related to our big leak ((string)), the next one is related to DOM allocations, and the last one is the Text constructor (the constructor for DOM leaf nodes containing text).
Select one of the HTMLDivElement constructors from the list then click pick Allocation stack.
Selected element in heap allocation results
BAM! We now know where this element was allocated (grow -> createSomeNodes). If we look carefully at each peak in the graph, we will notice that the HTMLDivElement constructor is called a lot. If we return to the Snapshot comparison view, we will notice that this constructor reveals a lot of allocation but no deletion. In other words, it regularly allocates memory without allowing the GC to reclaim any. These are all symptoms of a leak and in addition, we know exactly where these objects are allocated (the createSomeNodes function). Now it's time to get back to the code, study it and fix the leaks.

  • Another useful feature:

In the heap allocations result view, we can select Allocation view instead of Summary.
This view gives us a list of functions and the related memory allocations. We can immediately see grow and createSomeNodes stand out. When we select grow, we take a look at the constructors of the associated objects that are called by it. We notice (string), HTMLDivElement and Text which we already know are the constructors of the leaking objects.
The combination of these tools can go a long way in finding leaks. Play with it, run different profiling in your production sites (ideally code, non-minimized or obfuscated). See if you can find any leaks or items that are kept longer than they should (hint: they're harder to find).
To use these features, go to Dev Tools -> Settings and enable “record heap allocation stack traces”. It is necessary to do this before recording.

  • For further:

Memory Management – ​​Mozilla Developer Network
JScript Memory Leaks – Douglas Crockford (old, in relation to Internet Explorer 6 leaks)
JavaScript Memory Profiling – Chrome Developer Docs
Memory Diagnosis – Google Developers
An Interesting Kind of JavaScript Memory Leak – Meteor blog
Grokking V8 closures
Conclusion
Memory leaks can and do happen in garbage collected languages ​​like JavaScript. They can live in hiding for a while and eventually wreak havoc. For this reason, memory profiling tools are essential for finding memory leaks. Profiling should be part of development cycles, especially for medium to large applications. Start now to give your users the best possible experience.
Good debug!
Find the 1st article on this subject here !
[separator type=”” size=”” icon=”star”] [actionbox color=”default” title=”” description=”JS-REPUBLIC is a service company specializing in JavaScript development. We are an approved training center. Find all our technical training on our partner site dedicated to Training” btn_label=”Our training” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external =”1″]