5 Causes of Memory Leaks in Node.js and How to Fix Them

5 Causes of Memory Leaks in Node.js and How to Fix Them

Problem

Memory leaks occur when an application fails to release memory that is no longer needed during its operation. Initially, the application may run smoothly, but over time, it becomes slower and can even crash, displaying a "JavaScript heap out of memory" message somewhere in the console.

By default, V8 in Node.js allocates 4GB for dynamic data, also known as Heap. This limit can be increased, but at the cost of decreased application performance. Reference data types like Object, Function, and Array are stored in the Heap. Therefore, if too many instances of these objects are allocated during the runtime of the application, it can lead to memory overflow.

If you already know the underlying causes of memory leaks, here are 5 common issues that can cause memory leaks until there is no more space left to leak.

5 Causes of Memory Leaks

Global Variables

Global variables are variables declared with var or this, or variables not declared with any keyword. When not declared with a keyword, they are assigned to the window object in the browser.

function variables() {
  this.a = "Variable one";
  var b = "Variable two";
  c = "Variable three";
}

These variables will not be released by the garbage collector of V8 until they are set to null. Make sure you control the variables you create when declaring them globally. It's even better to use use strict to have the compiler warn you every time you declare a global variable.

Be careful when using global variables to store Objects or Arrays. They will not be released until you set them to null, or they can accumulate data to the point of losing control, thus occupying a large portion of the Heap memory.

Breaking Down Large Data Processing into Chunks and Spawning

What happens when you try to retrieve several million records from a database into an object? Or when you read a million rows in an Excel file and process them through 77 more steps? I can assure you that you will likely encounter a "Heap out of memory" message before you can continue processing. When loading such large amounts of data, the Heap quickly becomes filled up, leaving no room to store new data. Not to mention, processing data on such a large object will slow down your application and cause other issues.

There are various ways to handle this situation, but a common approach is to break the data into smaller chunks for processing. Additionally, to speed up processing, you can create additional child processes in Node using worker threads, as mentioned in the article What are Worker threads? Have you used Worker threads in node.js before?.

Be Careful with setInterval

Be Careful with setInterval

setInterval is a function that allows us to repeatedly perform a task at regular intervals. It's fine as long as you control the number of setInterval functions. However, if you fail to control the excessive tasks that they carry, the allocated memory can quickly get out of control. Therefore, make sure to call clearTimeout when setInterval is no longer needed.

const arr = [];

const interval = setInterval(() => {
  arr.push(new Date());
}, 1000);

clearInterval(interval);

Remove Unused Variables from Closure

Although closures are often debated for whether they cause memory leaks or not, the fact that they still retain the values of variables even after the function has returned indicates that the Heap still has to bear the cost of this storage.

Here's an example of a closure function:

const fn = () => {
  let Person1 = { name: "2coffee", age: 19 };
  let Person2 = { name: "hoaitx", age: 20 };

  return () => Person2;
};

After calling and executing fn(), Person1 will be released, but Person2 will not because it is still referenced in the returned function.

Unsubscribe from Observers and Event Emitters

Observers and Event Emitters have similar issues as setInterval mentioned above. Keeping observers for a long time can cause memory leaks. Make sure to unsubscribe from observers whenever you no longer need them.

Example:

const EventEmitter = require("events").EventEmitter;
const emitter = new EventEmitter();

const bigObject = {};
const listener = () => {
  doSomethingWith(bigObject);
};

emitter.on("event1", listener);

bigObject will be retained until the listener is unsubscribed.

emitter.removeEventListener("event1", listener);

Even Node.js itself warns about memory leaks if there are more than 10 event listeners attached to an event emitter.

emitter.on("event1", listener);
emitter.on("event2", listener2);
...  
emitter.on("eventN", listenerN);

// will receive a warning similar to
// "(node) warning: possible EventEmitter memory leak detected. N listeners added. Use emitter.setMaxListeners() to increase limit."

Summary

Most memory leaks are difficult to detect until your application suddenly crashes. In such cases, your task is to identify and fix the underlying causes as soon as possible. Based on the 5 points mentioned above, I hope it will help you in resolving those issues.

If you discover any other cases that can cause memory leaks and ways to fix them, please comment below and share with everyone!

References:

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.
Author

Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.

Did you find this article helpful?
NoYes

Comments (0)

Leave a comment...