Javascript Memory Leaks

Memory leaks are often associated with big enterprise RESTful services when an object is stored in memory but cannot be accessed by any of the running code. This causes diminishing performance ability of the application by reducing the amount of available memory for it to perform tasks (and could eventually lead to thrashing).

Since Javascript has normally been used for client side scripting, this has not been an issue because when a user refreshes a page or closes the browser the information about the entire page is removed from memory (obviously excluding cookies, browser storage capabilities, or etc.).

However since Javascript is more and more being used to create web applications using SPA architecture, AJAX calls, and other tools to provide a user experience similar to a desktop application, we start encountering (relatively minor) memory leak problems. I mean the worst thing is that a production user has to refresh the page right?

 Mark-and-Sweep Garbage Collection

While older browsers like IE6 and IE7 uses a reference-counting GC to handle circular references (DOM elements or parent-child scenarios), all modern browsers make use of the mark-and-sweep GC algorithm since 2012.

The MaS GC algorithm generally does the following:

 Common Memory Leak Sources:

 DOM

Since the DOM is a doubly-linked tree, having reference to any node in such a tree will retain entire tree from garbage collection. Think about an example of saving a DOM element in javascript and then later deleting that said element (or it’s parent/s element) but not the variable holding on to it. This causes a Detached DOM which holds reference to not only the said DOM element but the entire tree (since it’s doubly-linked).

Screen Shot 2016-03-20 at 3.38.16 PM.png

Solution: Determine which parts of the Detached DOM from the heap snapshot are not needed any more and remove references to them.

 Closure

Did you ever realize that when you don’t put a reference to an object (even a simple console.log), you won’t have access to it when you randomly drop a debugger or a breakpoint on that function you return even if it is under closure? It’s because that object has been garbage collected and was removed from memory, so having a reference will let you have access to it.

But what if you use eval() to evaluate javascript code represented as a string in your callback? Yes - that’s not really something you would do but hear me out. Now the GC marker will, because of closure, not mark any of the objects under closure and the said objects will remain in the application. So when the eval function needs it, it is there.

This can be replicated in a smaller case when using calls such as function.bind(this) and passing functions along to be executed later, these contexts will then be held in memory until the function is let go.

Solution: Closures actually work quite well (as demonstrated by dropping a breakpoint and not getting a reference to something not used). It’s more of a thing to keep in mind especially as we go to the third case of Events and Pub/Sub Pattern.

 Events and Pub/Sub Pattern

The Pub/Sub pattern is the breakout pattern of choice of the self-reliant components using the Observer and Singleton pattern. As a result, there exists an Object that lives throughout the application which contains event listeners. Subscribers that fail to unsubscribe however would result in memory leaks.

Solution: a removeSubscriber() call must be present on each subscriber (by the publisher) when it is done with the subscriber. Checking with Chrome Dev Tools’ timeline can isolate these problems by finding out which functions keeps progressively increasing the number of listeners.

Screen Shot 2016-03-20 at 4.19.58 PM.png

 Libraries and Frameworks

While you can delete all references you can think of to an object, the framework or library of choice would often still have their own references to it. In the example below, while you may have removed the element with id="harry" from the DOM, jQuery still retains a reference to it so that it could do the $.remove() function later on. You’d pretty much have to buy in to the framework or library of choice.

(() => {
    $('#harry').on('click', () => {
        console.log('potter!');
    });

    // Memory Leak
    const wizard = document.querySelector('#harry');
    wizard.parentNode.removeChild(wizard);

    // No Memory Leak
    // $('#harry').remove();
})();

Solution: Write vanilla javascript with minimal tooling (jk, but those who know me will know that would be my preferred answer). If you do have to go with the framework route, it’s a bit harder to debug since you’d have to find where you’re leaking and supply the proper clean up methods on the $destroy or off or whatnot.

 
22
Kudos
 
22
Kudos

Now read this

Angular + React = Performance

Angular and performance. When you do a Google search of these two words together, what you get it most likely blog posts about fixing performance issues or ways to avoid pitfalls. Theres no hiding the fact that the not-so-secret secret... Continue →