loading...

Dealing with Complex memory leaks in Javascript

groy profile image Greg Royan ・2 min read

Over the years working in Javascript I always assumed that garbage collection was magic. That the browser just understood what was no longer needed and cleaned up all references auto-magic-ally.

It wasn't until I worked on a deployed solution in Electron that I realized maybe the code I am writing is not as efficient as I once thought. Doing research on the subject I kept coming across articles claiming that Electron inherently is a memory hog. This made sense because I had always heard that Chrome and Chromium browsers contained some of the worst memory leaks in modern technology.

However having put all my eggs into the Electron basket at work, I was tasked with fixing a surprisingly large memory leak. And outlining that my choice had been the root cause of this leak was not the ideal solution. So, I scoured article after article on tracking down memory leaks in javascript. What caused them, how to find the root cause... and nothing was working.

After reading over many articles I finally found one that broke down closures in the clearest way possible. (Naturally, I believe I lost the reference to the original article)

Essentially, closures happen when functions are declared within the scope of an existing variable. These variables can be within another function, or as part of a JSON object. Once the parent object is dereferenced in the browser (garbage collected) the functions declared within the object are not cleaned up as expected. And because the originating reference was cleaned up, these functions are orphaned and left in memory until the browser is closed.

var stream = null;

var connect = function() {
  var streamer = this;
  function onaddstream(evt) {
    // Add stream to stream list
    streamer.add(evt);
  }
}

setInterval(function() {
  stream = connect();
}, 500);

In the above example, the stream variable would override the last connection created every 500ms. When the original connection is overridden I had assumed any references within the function would be garbage collected. However because the function onaddstream contains a reference to the streamer variable it cannot be cleaned up by the garbage collector.

We handled this by nulling out the functions on disconnect.

var stream = null;

var connect = function() {
  var streamer = this;
  var onaddstream = function(evt) {
    // Add stream to stream list
    streamer.add(evt);
  }
}

var disconnect = function(ctx) {
  ctx.onaddstream = null;
}

setInterval(function() {
  if (stream !== null) {
    disconnect(stream);
  }
  stream = connect();
}, 500);

Once I was armed with this information, I was able to completely clean up all memory leaks in the deployed application we had written. Our stability went from 3-5 days running, to letting the application run for months without needing a reboot.

If you are interested in knowing about other causes for memory leaks in Javascript check out the article by Nikhil posted on lambda test about Eradicating Memory Leaks in Javascript.

https://www.lambdatest.com/blog/eradicating-memory-leaks-in-javascript/

Posted on by:

groy profile

Greg Royan

@groy

Passionate about User Experience. Frontend developer and team lead.

Discussion

markdown guide