Beyond Memory Leaks in JavaScript

Daniel Reis
OutSystems Experts
Published in
11 min readAug 5, 2016

Memory leaks are like that pimple you have on your nose with prom night coming. You know that they exist and they’re not going anywhere, but you just pray they don’t flare up or that no one else notices when the time for magic comes.

They’re uncomfortable. They’re nasty. They feel like a beacon announcing your nose to the world from a distance. And all you ever wanted was to impress ladies and gents alike with your smashing dance moves.

(For the record, I had no pimples on prom night. And I danced like a champion.)

Every front-end developer, even those at OutSystems, has faced JavaScript memory leaks one time or another. In this regard, there are three types of developers:

  1. The Savvy: Those who knew what they were dealing with and solved the issue.
  2. The Aware: Those who knew what they were dealing with but had no idea how to solve the issue.
  3. The Oblivious: Those who didn’t even realize there was a problem, to start with.

With this in mind, we have compiled a few guidelines to help those #3s leave the comfort of their oblivion and help #2s delve a little deeper. And, you never know, but those clever #1s may also enjoy, and hopefully endorse, some of these tips. We’re open for discussion, too. We’ve got treats for everyone!

TL; DR: If you want to know where to start regarding those dreadful JavaScript memory leaks, you’ve come to the right place.

The Big Picture — JavaScript’s Memory Lifecycle

First things first, then. In JavaScript, memory is automatically allocated each time you create, for instance, an object, an array, a string, or a DOM element.

Memory leaks happen when your code needs to consume memory in your application, which should be released after a given task is completed… but isn’t. For some reason, the application neglects to release the memory, which keeps on being consumed without true need for it to happen.

This is when that pimple first manifests itself and when you should take measures to prevent it from flaring or spreading out.

The biggest problem with memory leaks is that they’re inherent code flaws, not errors with outputs that you can verify. They’re dubious, because they result from valid code that compiles, which makes the runaway, memory-leaking code seem intentional for the machine. They’re definitely not features and are not easy to debug.

However, if your app is running slowly or even crashing unexpectedly, that’s the first clue that you may have a memory leak.

A sawtooth pattern with these vertical drops coinciding with garbage collection may also indicate a memory leak.

Memory leaks can assume many shapes and forms, though. They’re masters of deception and disguise. They move in mysterious ways. But it’s OK, it’s alright: we’re here to help.

Object Memory Graph and Retaining Paths

The object memory graph in JavaScript is a collection of all objects and their references. The reference path from the most immediate context up to the top is called a retaining path.

You can represent memory leaks in this graph. What do you call those values that you no longer need, yet are still referenced in this graph? Want to guess this one?

They’re called memory leaks. That’s right.

The retaining path is what classified the object as memory. If paths are cut off, the object becomes available for garbage collection. So, all values that cannot be reached from the root node without a retaining path are garbage-collected.

You have a nifty graphic representation below for that. See that dashed, isolated, sad little object down there? That one will be swept away.

That yellow value has a retaining path up to root, but it’s no longer needed… therefore it’s a memory leak.

It’s Time to Take Out the Trash

Note from editor: Speccy game screenshot gets Daniel extra coolness points

So, what is this garbage collection thing? Picture this: if you forget your trash inside the house, when it’s time for your trashman to come he won’t find anything outside to collect. He won’t barge into your house to fetch it. Unless you have that kind of relationship, but you may be out of the norm there. Just sayin’.

By the way, you know that garbage bag you’ve forgotten inside the house? It will start smelling pretty bad, soon. If you’re not careful, the garbage bag may even leak. You know where this is going.

Garbage collection is an automatic memory management process that operates in runtime. It uses a mark-and-sweep algorithm that tracks all the paths and finds all objects in the object graph tree that are referenced from their roots.

Any objects that are not inside your “house” are considered garbage, so your personal JavaScript trashman will collect and reclaim the memory it used.

Here we can see Chrome’s timeline garbage collection in action, freeing some memory that was allocated to objects that are no longer referenced. This happens when the garbage collector’s heuristics decide it’s the right time to do it, so we have no control over when a garbage collector runs. It’s like a surprise mother-in-law visit, but one where she cleans your place up and then leaves.

Even so, garbage collection is not prevention. It’s an after-the-fact process that cleans the mess. When you have too much garbage to collect, you can start experiencing freezes.

The 3 Most Common Memory Leaks and How to Avoid Them

What you really want is a pore-cleansing tissue that will keep your face squeaky clean and avoid that embarrassing pimple-on-the-nose-on-prom-night thing. Here are three types of “tissues” to avoid issues:

Accidental Global Var

While deployed in the field, the most common memory leaks I kept identifying were caused by accidental variables. If you reference an undeclared variable, you will create a global var, which will be used within the global scope.

The simplest way to avoid this is to enforce the “use strict” directive. Strict mode doesn’t allow you to use undeclared variables. This is true pore-cleansing stuff right here; it acts like a protector inside a script file or given function.

If you need to use or declare a global var inside a function, there’s a convention for that: deliberately attach the var to the window. Conventions are cool for readability and future maintenance, and for collaborative work, too. You know I endorse being nice to your colleagues.

Closures

One of the hardest hurdles to clear when it comes to JavaScript’s memory management is easily closures by timers (setTimeout or setInterval).

Any object inside the timer will hold a reference in order to run that piece of code somewhere in the future without any problems. This is accomplished by enclosing all variables needed in a private data structure that from then on is exclusively accessed in runtime by JavaScript itself. Meaning, it’s privately used by JS, so you will have no access to it. Keep an eye out for those cases in the future.

There’s a great Smashing Magazine article I always recommend, where Addy Osmani describes this brilliantly: Writing Fast, Memory-Efficient JavaScript. It’s an easy and fun read, because Addy explains it so well; you should really have a look.

Personally, this was a great reference for me, back in the day. It had great tips about memory performance optimization, which lead to try to gather more in-depth information regarding this matter.

Lingering DOM References to Removed Elements

A DOM element belongs to the DOM, but it also exists in the Object Graph Memory. Therefore, if you delete the former, you should else delete the latter.

In this example, after you click trigger, elementToDelete is removed from the DOM. But since it’s still referenced within the listener, the allocated memory for the object is still used.

Do you remember the dashed example inside the object memory graph? The sad little object that was cut off and had no reference path?

Well, in this example, I put the var elem inside the listener, which makes it a local variable. When elem is deleted, the path for the object is cut off. The trashman can thus reclaim this memory.

Left: bad. Right: good!

As you can see, if you delete the function in the left example, the value keeps a retaining path to the root. If you delete the function path in the right example, the value goes away gracefully.

3 Ways to Identify Memory Leaks With Chrome DevTools

The previous DOM and lingering object memory graph references are good material for the following exercises, so we’ll use them as examples. This is when it gets real; we’re getting our hands dirty.

Timeline

For the first example on how to find memory leaks I will use Chrome DevTools Timeline. Although there are alternatives to DevTools, these are the ones I use and endorse. These tools are so cool they should be named Chrome DevCools instead.

After removing the element, I am going to force garbage collection to see whether it can reclaim the memory that was allocated to the detached element.

Notice how that graph didn’t reset all the way to the initial value? (Shhhh… that’s a memory leak.)

As you can see in that cool little gif up there, garbage collection wasn’t successful. JS heap cannot return to the initial allocated memory value, since the object still has its retaining path.

In sum, this is what I did there:

  1. Timeline > Start recording.
  2. Click Trigger to remove element from DOM.
  3. Click the basket case icon (it starts the garbage collection process manually.)
  4. Stop recording.
Notice how that graph went all the way back to the initial value? (Shhhh… that’s how it’s supposed to work)

Here, I followed the same process as before but ran the code with the var reference inside the function. In this case garbage collection reclaimed the memory with success, since JS heap does return to the initial allocated memory value.

Left: bad. Right: good!

See the difference? This gives you a good visual indication on whether there are memory leaks or not.

Snapshot Compare

In this second example, I will use the Chrome DevTools Heap Snapshots. I am going to take a snapshot and run the code for the detach. Then I will take another snapshot, and check for differences.

This feature will compare the two snapshots and will show the differences between them. In this example, we see the elem inside detached DOM tree.

Keep an eye out for those objects with yellow highlights as that’s what identifies when they’re referenced. The red highlights means they are referenced by the yellow. So, if you focus on the yellow, you’ll take care of the red in one sweep.

  1. Profiles > Take a snapshot.
  2. Click Trigger to remove element from DOM.
  3. Take another snapshot.
  4. Compare the snapshots with dropdown selector.
  5. Search for detached DOM tree elements.

And here we’re running the code with the var reference inside the function. As you can see, the comparison doesn’t detect any detached references.

Allocation Profiler

Last but not least, in this one, I recorded the allocation of a carousel and used the Chrome DevTools Allocation Profiler to analyze the spikes.

Keep your eyes peeled for this one, because this subject has enough substance for an entire article. And I’m very interested in writing about this in the future, so we’re not going in as deep for this as one, as we did for the others.

The blue spikes show when memory is allocated for something. The gray ones represent allocation memory after the garbage collector reclaims the memory for that specific allocation. Watch out for those blue spikes.

Final Notes

Since the garbage collector reclaims memory for execution and you can’t control when it happens, it may run while something else important is running, too. How many times have you been scrolling a page in your mobile phone and experience some kind of stutter or freeze, even if just for a few seconds? That may just be the trashman collecting your garbage.

Don’t leave your trash in the house; it will leak and smell. Pimples do come from “garbage” that accumulates on your pores, you know? (Yeah, I needed to tie it all in, just like that.)

Now, while having your garbage collected is a good thing, because it frees up memory that no longer needs to be allocated, if performance suffers you may need to find ways to minimize that impact.

There are workarounds for optimizing your memory usage. Stuff like Object Pooling, which helps minimize the garbage collector impact on performance: You create a pool of objects and reuse them instead of recreating them for garbage collection.

Garbage collectors nowadays are extremely optimized, so I only recommend using this if you’ve noticed it running too many times for your taste and that it’s taking a long time to run each time.

Now, I’m not going to suggest that you start changing your code mindlessly. Take one step at a time; there’s a lot of reading for you to do still. Check all those references I’m going to leave you all down there.

It might prove impossible to avoid garbage completely. You just need to take this into consideration; make sure you’re aware of the signs and what they mean. Run periodic audits, too. Above all, remember this very important thing: not everything is a memory leak.

If it’s not cramping your style — making your apps slower — it may not be a memory leak.

Code needs memory to run, and sometimes it needs more memory to run better. Sometimes that ever-growing amount of memory consumed makes sense, you just have to ask yourself whether the result of that memory usage is what you expected or not. If it’s not, it might be time to think about… recycling.

Daniel Reis | Mobile and Front-End Expert at OutSystems

Memory saved? Garbage collected? Pimples popped!? Help your friends avoid memory leaks and acne on LinkedIn | Twitter | Facebook | Email

--

--