JavaScript Memory Management

JavaScript Memory Management

Master the art of memory management in JavaScript.

Hello developers! We know that JavaScript is by far the most used programming language among developers due to its flexibility and ease of use. However, efficient memory management remains a challenging task. This is frequently ignored despite being as necessary as air to our JavaScript programs. It acts as the quiet hero in the background, ensuring our code doesn't crash.

But we don't usually see it in action, unlike the air we breathe. To make the topic easier to understand and clear, we will simplify it. Now let's explore the topic.

What is Memory?

Firstly, we will see what memory means in the JavaScript language. We can consider it a sort of storage where the objects, functions, and variables are kept while our program executes. However, as this memory is finite, effective management is essential for an application's smooth functioning.

When an object is created in JavaScript, memory is automatically allocated, and when it is no longer needed, memory is released (a process called garbage collection). This gives developers a false perception that memory management is not a concern for them. When memory is allocated, all of the memory allocations are recorded by JavaScript.

So, the memory lifecycle consists of three stages:

Allocation of memory

Let's say we want to create a cart list for our e-commerce application. So, items added to the cart will be stored in a variable called cart, as an array. There is no need for web developers to take any action because Javascript does the memory allocation automatically.

Using memory

By using the variable name, we can access the values stored in memory. If a function has been allocated memory, we can call it with arguments, if any.

Releasing memory

Garbage collection is the method used to free up the memory. The majority of problems emerge during this stage of the memory cycle. Here also, there is no need for developers to take any action because Javascript releases the memory automatically.

const cart = [1, 2, 3, 4]; // Allocation phase in which memory is allocated

if (cart[0] === 1) {
  // Access phase in which the memory is used
}

Stack and Heap

We have seen how memory is created and released in JavaScript. But once data or values are created in memory, where are they stored? JavaScript uses two areas for data storage: stack and heap.

The stack is used to store static data like variables, function scope, and function calls. It follows the LIFO (Last In First Out) principle.

On the contrary, dynamic data with a dynamic lifetime, such as objects, closures, and data structures, are kept in the heap. The allocation and deallocation of memory in this memory storage is quite complicated.

Garbage Collection

We are aware of what is garbage. In simple terms, items that are no longer usable are referred to as garbage. Garbage collection in programming language refers to the process of clearing out memory locations that aren't storing any valuable information and then reallocating those spaces to hold active and valuable data.

It is impossible to remove some values. These values are referred to as reachable values. There is a guarantee that they will exist in the memory and that they are somehow accessible.

The JavaScript engine has an entity called a garbage collector that keeps track of every object and removes unreachable ones.

Certain algorithms govern this process, which are -

Reference Counting

Earlier JavaScript engines used to have a garbage collector that used a reference counting algorithm. Suppose there are two objects. If the first object has access to the properties and methods of the second object, then the first object will be considered to have a reference to the second object.

The reference count of an object or variable goes to zero when it is no longer in use, allowing the garbage collector to safely remove it from memory.

However, there is an issue with this approach. If two or more objects refer to each other, then it causes a circular reference.

let obj1 = { name: "John" };
let obj2 = { name: "Doe" };
obj1.friend = obj2;
obj2.friend = obj1;

This way the object will always have a reference count of at least 1 and that memory is never freed by the garbage collector.

Mark and Sweep Algorithm

In this algorithm, objects that are no longer in use, i.e., objects that are not reachable, are identified and removed by the JavaScript engine using a technique known as the mark-and-sweep.

It occurs in two stages. In the first stage, known as the mark phase, it first searches the root object; for instance, the window object, for every object that is reachable before moving on to the child and finally the grandchild objects. The second stage, known as the sweep phase, involves removing the memory and sweeping away any items that are no longer in use.

This method solves the circular reference problem as it doesn’t count the references, but rather checks whether the object is accessible from root or not.

Most of the modern JavaScript engines are shipped with mark-and-sweep garbage collectors.

For example, suppose we have multiple variables and functions in our memory.

When using the mark and sweep technique, the garbage collector first selects and marks the "roots” (global variables, currently executing function), then moves downward and marks the references that pass through it and identifies every object that is "reachable." If the currently executing function is getData and the garbage collector is marking references, then values 'John' and 'Welcome John' are unreachable and deleted from memory.

Memory Leaks

Memory leaks can happen when objects are unintentionally kept in memory after they are no longer required.

Incorrect closure usage, unexpected global variable declaration, and improperly removed event listeners are just a few of the potential causes of memory leaks. These may result in higher storage usage and reduced application performance.

Also the Chrome Memory Tab, in DevTools, is where we can find memory leaks by comparing heap snapshots.

Best Practices for Memory Efficiency

  • Instead of using var to declare the variables, use let, for variables that are reassigned values and const, which are not reassigned.
// Bad
var name = "John Doe";
name = "Jane Doe";
var age = 30;

// Good
let name = "John Doe";
name = "Jane Doe";
const age = 30;
  • Variables created without the use of the let, var, or const keywords may remain in the memory for an extended period, possibly leading to memory leaks.
// Bad
const getName = () => {
  name = "Emily Cooper";
};

// Good
const getName = () => {
  let name = "Emily Cooper";
};
  • Properly disposing of the event listeners could be beneficial.
let element = document.getElementById("myElement");
element.addEventListener("click", function () {
  console.log("Button Clicked!");
});
element.removeEventListener("click", function () {
  console.log("Event Listener Removed");
});

Wrap Up

A challenging but essential part of web development is JavaScript memory management. Writing effective, scalable, and error-free code requires a thorough understanding of memory allocation, release, and optimisation. Developers may create high-performing online applications and handle the complexities of JavaScript memory management by implementing best practices.

Resources