4 Types of Memory Leaks in JavaScript and How to Get Rid of Them 1/2

b
Find out about memory leaks in JavaScript and what you can do to fix them!
In this article, we will explore memory leak typologies in client-side JavaScript code. We'll also learn how to use Chrome's developer tools to find them. Let's start!

Introduction

Memory leaks are a problem that all developers face, even when working with languages ​​that manage memory, there are cases where memory leaks occur. Leaks are the cause of a whole family of problems: slowdowns, crashes, high response times, and even problems with other applications.

What is a memory leak?

A memory leak can essentially be defined as a memory area which is no longer used by an application but which is not freed and therefore not made available to the OS. Languages ​​have different methods of managing memory. These methods reduce the chances of a memory leak occurring. Nevertheless, knowing whether a memory area has become useless or not is a potentially insoluble problem. Only designers are able to clarify if a memory area is unused. Wikipedia has good articles on manual and automatic memory management.

Memory management in JavaScript

JavaScript is one of the languages ​​with Garbage collection. Garbage collection languages ​​help developers manage memory by periodically checking which previously allocated memory area should remain accessible by other parts of the application. In other words, garbage collection-based languages ​​reduce the memory management problem from "what memory area is still required" to "what memory area remains accessible to the rest of the application". The difference is subtle but important: while only the developer knows which memory area should remain accessible in the future, memory areas that have become inaccessible can be algorithmically identified and marked to be returned to the OS.

Garbage-free languages ​​typically use other techniques to manage memory: explicit memory management, where developers tell the compiler when a memory area is no longer required, and reference counting, in which a count of usage is associated with each memory block (when it reaches zero, the area is returned to the OS). These techniques come with their own counterparts (and potential risks of leaks).

Leaks in JavaScript

The main reason for leaks in Garbage collection languages ​​are unwanted references. To understand what unwanted references are, we must first understand how the Garbage collector determines whether a memory area is accessible or not.

“The main reason for leaks in Garbage Collector-based languages ​​are unwanted references”

 

Mark and sweep

Most garbage collectors use an algorithm called mark-and-sweep. The algorithm consists of the following steps:

  1. The Garbage collector builds a list of “roots”. Roots are usually global variables that are referenced in code. In JavaScript, the "windows" object is an example of a global variable that can act as a root. The Windows object is always present, so the Garbage Collector can consider it - and all of its children - always present (ie, not Garbage).
  2. All roots are inspected and marked as active. All children are also inspected recursively. Anything that can be accessed from root is not considered garbage.
  3. All memory areas that are not marked as active can then be considered as garbage. The Collector can then release these memory areas and return them to the OS.

Most modern garbage collectors are built on this algorithm with variations but the principle remains the same: all accessible memory areas are marked as such and the rest is considered garbage.
Unwanted references are references to memory areas that the developer knows no longer use but which – for a whole host of reasons – remain in the tree of an active root. In the context of JavaScript, unwanted references are variables that are kept somewhere in the code that will no longer be used and point to a memory area that could have been freed. For some, it's a developer error.
In order to understand what are the most common leaks in JavaScript, you have to look at how references are often forgotten.
a

The four types of JavaScript leaks:

1: accidentally global variables

One of JavaScript's goals was to develop a language that looked like Java but was permissive enough to be used by beginners. This is reflected in the way JavaScript handles undeclared variables: a reference to an undeclared variable creates a new variable in the global object. In the case of a browser, the global object is window. In other words:
function foo(arg) {
bar = "this is a hidden global variable";
}
Is in fact:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
If bar was supposed to contain the reference to a variable only inside the scope of the foo function and you forget to declare it, a global variable is created by accident. In this example, leaking a String isn't very dangerous, but it could be much worse.
Another way to accidentally create a global variable is:
function foo() {
this.variable = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();

To prevent these errors from appearing, add 'use strict'; at the beginning of your JavaScript files. This triggers a stricter JavaScript parsing mode that prevents globals by accident.

Note on global variables
Although we talk about unsuspected Globals, the reality is that a lot of code is littered with explicit global variables. They are by definition non-collectible (unless they are nullified or reassigned). In particular, global variables used to store and process large amounts of information are of concern. If you need to use global variables to store a lot of data, be sure to null them or reassign them when you're done.
A common cause of increased memory consumption in relation to Globals is caching. The cache stores data that is used repeatedly. For this to be effective, the cache must have a larger size limit. Caches that grow without bounds result in high memory consumption because content cannot be collected.

2: forgotten timers or callbacks

Using setInterval is quite common in JavaScript. The other libraries provide observers and other systems that support callbacks. Most of these libraries make callbacks inaccessible after their instances themselves become inaccessible. In the case of setInterval, it is common to see code like this:
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// Do stuff with node and someResource.
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
This example illustrates what can happen with dangling timers: timers that reference a node or data that is no longer needed. The object represented by the node may be deleted in the future, rendering the entire block inside the range handler useless.
However, the handler, just like the interval, is still active and therefore cannot be collected (for that, the interval would have to be stopped). If the range cannot be collected, then neither can its dependencies. This means that the resources, which store potentially quite substantial data, cannot be collected either.
In the case of observers, it is important to make calls explicit to remove them when they are no longer required (or the associated objects are about to become unreachable). In the past, this was particularly important as some browsers (IE6) were not able to handle cyclic references well (see below for more info on this)
Today, most browsers can and will collect observer handlers once the observed object becomes unreachable. It is good practice, however, to delete observers before the object is deleted. For example :
var element = document.getElementById('button');
function onClick(event) {
element.innerHtml = 'text';
}
element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.
Note about object observers and cyclic references
Observers and cyclic references have long been a JavaScript developer's nightmare. This was due to a bug (or design decision) in Internet Explorer's Garbage collector. Older versions of IE could not detect cyclic references between DOM nodes and JavaScript code. This is characteristic of observers, which generally keep a reference to the observable (as in the example above). In other words, every time an observer is added to a node in Internet Explorer, it leads to a memory leak. That's why developers started manually removing handlers before nodes or nulling references in observers. Today, modern browsers (including Internet Explorer and Microsoft Edge) use more modern garbage collection algorithms that can detect these cycles and process them correctly. In other words, it is no longer necessary to call removeEventListener before making a node unreachable.
Frameworks like jQuery remove listeners before releasing nodes (when they use their specific API for that). It's handled internally by the library and ensures that it doesn't generate any leaks, even on older browsers like the old Internet Explorer.

3: References outside the DOM

Sometimes it can be useful to store DOM nodes in data structures. Suppose you want to quickly update the contents of multiple columns in a table. It might make sense to store a reference to each DOM column in a dictionary or an array. When this happens, two references to the same DOM element are kept: one in the DOM tree and the other in the dictionary. If at some point in the future you decide to delete these columns, you must make both references inaccessible.
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// Much more logic
}
function removeButton() {
// The button is a direct child of body.
document.body.removeChild(document.getElementById('button'));
// At this point, we still have a reference to #button in the global
// elements dictionary. In other words, the button element is still in
// memory and cannot be collected by the GC.
}
Another thing to consider are references to internal nodes or leaves of the DOM tree. Suppose, for example, you keep a reference to a specific table cell (a tag) in your JavaScript code. At some point, you decide to remove the specific table from the DOM but keep the reference to that cell. Intuitively, one would think that the GC will correctly collect everything but this cell. In practice, this will not happen: this cell is a child node of the array and the children keep references to their parents. Which means that the whole table will remain in memory, because of the JavaScript reference to this cell. Take this into account if you keep references to DOM elements.

4: Closing

A key element of JavaScript development is closure: anonymous functions that capture variables from the parent scope. The Meteor developers encountered a special, rather subtle case of memory leak due to the details of the Javascript runtime implementation:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
This snippet does one thing: whenever replaceThing is called, theThing retrieves a new object that contains a large array and a new closure (someMethod). At the same time, the unused variable contains a fence that has a reference to originalThing (theThing from the previous call to replaceThing). Already a little complicated, right? The important thing is that once the scope is created for the fences, which are in the same parent scope, this scope is shared.
In this case, the scope created for the closure someMethod is shared with unused. Unused has a reference to originalThing. Although unused never be used. someMethod can be used through theThing. And like someMethod shares the scope of the fence with unused, even if unused is never used, its reference to originalThing forces it to remain active (prevents its collection).
When this snipet runs repeatedly, we see a steady increase in memory usage. And it does not reduce with the passage of the GC. In essence, a closing linked list is created (rooted by the variable theThing) and each scope of these closures contains a reference to the large array, which generates a consequent leak.
This is an implementation artifact. Another implementation of fences that would handle this problem is conceivable as explained in the meteor-blog.
Original article de Sebastian Peyrott translated by JS Staff
 
Don't miss the follow-up to the next episode: how to fix memory leaks 🙂
[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″]