Avoiding Memory Leaks in Javascript

Avoiding Memory Leaks in Javascript

From Detection to Solution: Solving Memory Leaks in Your Code

Β·

6 min read

- Introduction 🏁

Memory leaks are like slow-growing monsters that creep up on you when you least expect them. They quietly occupy memory, slowly sucking the life out of your program until it can no longer function. At first, you might not even notice they're there, but over time they'll grow stronger and stronger until they've taken over your program. It's up to you, the programmer, to keep these memory leaks in check, freeing memory when you no longer need it, so your program can continue to run smoothly. If you neglect your memory management duties, you'll soon find that your program has become a victim of these memory leak monsters.

- Overview πŸ‘

The key factor in memory leaks is the retention of memory references that are no longer needed by the application. As long as a reference to an object or data type is maintained in memory, it will be prevented from being garbage collected, and can contribute to a memory leak.

πŸƒβ€β™‚οΈ Let's see an example:

const newArr = [];
for (let i = 0; i < 100000000; i++) {
    newArr.push(i);
}

In this example, we have a for loop that pushes a large amount of data into an array called newArr. After the loop has finished, the newArr still holds a reference to all the data, even though it is no longer needed. This memory can't be freed by the garbage collector, because the array still holds a reference to it. As a result, the program will continue to consume more and more memory, eventually leading to a crash or performance issues.

Another example of a memory leak is when using global variables or creating a closure that keeps a reference to an object.

let obj = {};
function myFunction() {
    obj.data = 'hi';
    setTimeout(myFunction, 1000);
}
myFunction();

In this example, myFunction creates a closure that keeps a reference to obj. The function sets a timeout to call itself every second, but it never clears the reference
to obj, so the garbage collector can never free the memory associated with it. Memory leaks can be tricky to identify and fix, but using tools such as the browser's developer tools, heap snapshots, and performance profilers can help you identify and fix memory leaks in your code. It's also important to keep in mind best practices such as minimizing global variables and avoiding closures that keep references to unnecessary objects and also to avoid creating circular references.

If your web app has to listen for keyboard events, mouse events, and scroll events, you should remember that all these are patterns that can easily leak memory with
a buggy code.

An event listener prevents objects and variables captured in its scope from being garbage collected. Thus, if you forget to stop listening, you can expect a leak in memory.

addEventListener is the most common way in JavaScript to add an event listener, which will remain active until:

  • you explicitly remove it with removeEventListener(), or

  • the associated DOM element will be removed.

function myFunction() {
  // ...
}
document.addEventListener('keydown', myFunction); // add listener
document.removeEventListener('keydown', myFunction); // remove listener

In the above example, you can’t remove the document. That means you’ll be stuck with the myFunction() listener and whatever it keeps in its scope if you won’t clean up by calling removeEventListener().

In case you need to fire your event listener only once, you can add a third parameter { once: true } to addEventListener(), so that the listener function will be automatically removed after performing its job:

function myFunction() {
   // ...
}
document.addEventListener('keyup', myFunction, {
    once: true
}); 
// the listener will be automatically removed after running once

You might not know that you caused memory leaks until they show up in production and eventually wreak havoc.

Here are the common patterns to recognize it:

  1. Slowdowns: After a long session (could be hours or even a day) of working with the application, the UI becomes slower and sluggish.

  2. The web page crashes.

  3. The app pauses frequently.

  4. The JS heap ends higher than it began.

  5. You see an increasing node size and/or listeners size.

We can break down the memory usage in this performance timeline record by:

  • JS heap (blue line): It represents the memory required by JavaScript. In this sample, it ends higher than it began (sign for a memory leak).

  • Documents (red line)

  • DOM nodes (green line): It ends higher than it began (sign for a memory leak).

  • Listeners (yellow line): It ends higher than it began (sign for a memory leak).

- How to avoid them? πŸ€”

There are a few ways to copy objects without causing memory leaks.

const source = {
    a: [1, 2, 3],
    b: {
        c: 'Hello'
    }
}

// Using Object.create()
const target1 = Object.create(source)
target1.a.push(4)
console.log(source.a) // [1, 2, 3, 4]

// Using Object.assign()
const target2 = Object.assign({}, source)
target2.a.push(5)
console.log(source.a) // [1, 2, 3, 5]

As you can see, in the example above, target1.a is referencing the same array as source.a, so when we modify target1.a, it also modifies source.a. However, target2.a is a new array created by Object.assign(), so it does not modify the original array in source.a

If you are using Lodash, you can easily perform complex operations with just a few lines of code. This can help you write more maintainable code and save time on debugging.

const source = {
  a: [1, 2, 3]
};

const target = _.cloneDeep(source);
console.log(source.a === target.a); // false

in this example, target is created using cloneDeep with source as the source object. The properties of source are copied to target, and changing a property in target does not affect the value of the same property in source.

- How to track? πŸ’‘

  • Using the Chrome and Firefox DevTools Performance Monitor can be used to detect JavaScript memory leaks in real-time. To use it
  1. Click on the three dots in the right menu

  2. More tools

  3. Choose performance monitor

  • Using Memory Snapshots: Take a memory snapshot at different points in your code, and compare them to identify objects that have been leaked.

  • Using a Leak Detector Library: There are libraries like memlab that can help you identify memory leaks in your JavaScript code.

you can see Addy Osmani's video in JavaScript Memory Management Masterclass gives you a deep dive into how to think about memory management with coverage of the sawtooth pattern and GMail's "three snapshot" technique. and for an added dose of detective work, try this Heap Snapshots demo. Get clued up on detecting mem leaks with a quick video tutorial! Check out egghead.io top-notch video using DevTools: javascript-easily-detect-memory

- Conclusion πŸ™Œ

Memory leaks in code are like a slow poison - they may not cause immediate harm, but over time, they'll degrade performance and eventually lead to a crash. Don't let these sneaky leaks get the best of you - stay proactive by following memory management best practices, using monitoring tools, and regularly reviewing your code. With these steps, you can keep your programs running strong and avoid any leaks from seeping in.

- References πŸ“„

Thanks for stopping by! πŸ‘

Β