Beyond Memory Leaks in JavaScript

Daniel Reis
Aug 5, 2016 · 11 min read
Image for post
Image for post

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.)

Image for post
Image for post

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.

Image for post
Image for post
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.

Image for post
Image for post

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

Image for post
Image for post
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.

Image for post
Image for post

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:

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.

Image for post
Image for post

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.

Image for post
Image for post

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.

Image for post
Image for post

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.

Image for post
Image for post

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.

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.

Image for post
Image for post

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?

Image for post
Image for post

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.

Image for post
Image for post
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.

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.

Image for post
Image for post
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.
Image for post
Image for post
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.

Image for post
Image for post
Left: bad. Right: good!

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

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.

Image for post
Image for post

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.
Image for post
Image for post

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.

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.

Image for post
Image for post

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.

Image for post
Image for post
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

OutSystems Experts

Digital transformation with a low-code platform…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store