javascript

We all know that garbage collection (GC) is important for modern application development. It depends on your programming language and many developers have little idea how it is done.

Garbage collection always releases memory that is no longer in use. The strategies and algorithms to achieve this vary from one language to another. For example, JavaScript does this in some interesting ways, depending on whether you’re on a browser or a Node.js server.

But have you ever considered how this process works behind the scenes? Let’s take a moment to understand the magic of JavaScript GC in the browser and on the server.

Memory cycles

The reason we need GC is because of the memory usage during programming. You create functions, objects, etc. that take up memory space.

The great advantage of JavaScript over C, for example, is that it performs memory allocation for you automatically. The process is very simple and requires only three clear steps.

Memory cycles

Yes, but where does JavaScript store this data? Basically there are two destinations, the first is the memory heap memory heap and the second is the stack stack.

Heap is another term that everyone has heard of. It is responsible for allocating dynamic memory for us. In other words, this space is reserved for JavaScript to store things like objects and functions without limiting the amount of memory it can use.

One difference with a stack is that this is a data structure used to literally stack elements, such as references to real objects from raw data. The stack allocation policy is “safer” because it knows how much memory is allocated and is fixed.

Consider the following code example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// heap and stack
const task = {
  name: 'Laundry',
  description: 'Call Mary to go with you...',
};

// stack
let name = 'Walk the dogs'; // 1
name = 'Walk; Feed the dogs'; // 2
const firstTask = name.slice(0, 5); // 3

Each time a new object is created in JavaScript, the space in heap memory is dedicated to it.

When it comes to special cases, such as using invariant values (as in the original language in JavaScript), the language always favors reallocation using the previous memory slots.

The following is an explanation of points 1-3 in the code example above.

  1. simply create a new original variable using a string value.
  2. overwrite its value with the new value one. When this happens, JavaScript assigns a new spot on the stack instead of using the current stack.
  3. no matter how many times you do this, whether by direct assignment or by the result returned by the method, JavaScript will always do the same thing.

JavaScript’s garbage collection algorithm

Now we know how JavaScript handles memory allocations and what’s in them when they are allocated. But how does it release them you?

JavaScript’s garbage collector takes care of it, and the process is as simple as it sounds: once an object is no longer in use, the GC releases its memory.

It’s actually not that simple, how JavaScript knows which objects are collected. That’s where the algorithmic scenario comes in.

Reference Counting GC

As the name suggests, this strategy works by searching through memory for resources that have zero references pointing to them.

Let’s look at the previous code snippet as a reference to get a better understanding.

1
2
3
4
5
6
const task = {
  name: 'Laundry',
  description: 'Call Mary to go with you...',
};

task = 'Walk the dogs';

This is an object that has multiple internal properties task. Then let’s assume that another developer decides that task can simply use the original language representation task = 'Walk the dogs'. So, the first task object no longer points to its reference, which makes it available for GC.

This sounds simple, but in practice there is much more to it than that.

There is a special edge case you need to know about: circular dependencies. You may never have thought about them before, because JavaScript knows how to handle them too. But usually, they happen in this way.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function task(n, d) {
    // ...

    reporter = { ... };
    assignee = { ... };

    reporter.assignee = assignee;
    assignee.reporter = reporter;
};

myTask = task('Laundry', 'Call Mary to go with you...');

This may not represent a functional task in a real application, but it is sufficient to imagine a situation where the internal properties of two objects refer to each other.

This would create a loop. Once the function completes, JavaScript’s reference counting GC will not be able to free or collect the two objects because they are still referencing each other.

This is a common scenario that can easily lead to memory leaks in real applications. To avoid this, JavaScript provides us with a second strategy.

Tagging and scanning algorithms

The tag-and-scan algorithm is known for its use in many programming languages for garbage collection. In short, it makes use of intelligent methods to determine if a given object can be reached from the root object.

In JavaScript, if you are on a Node.js application, the root object is the global object; if you are on a browser, it is the window.

The algorithm starts at the top, drops down the hierarchy, and marks each object in turn that is reachable (i.e. - still referenced) and cleans up those objects from the root object that are not scanned.

node.js how to free memory

Node.js, like Chrome, is based on V8, Google’s open source JavaScript engine. The important point is the allocation of heap heap.

Let’s take a look at the following representation.

nodejs New space vs. old space.

The Node.js heap is divided into two main parts: the new space and the old space. The former is where new objects are allocated, while the latter is where they are stored for the long term.

As a result, garbage collection of objects in the new space is faster than in the old space. On average, it takes up to 20% of the objects referenced to get into the old space.

Because of this feature, V8 makes use of an additional GC strategy: scavenger.

Scavenger

As we have seen, it is more expensive for Node to release things in old space. When it has to do so, the mark and scan algorithm runs to achieve the goal.

The scavenger GC specializes in collecting garbage from new generations. Its strategy consists in selecting long-lived objects and moving them to the so-called old space. To achieve this step, V8 ensures that at least half of the new generation remains empty; otherwise, it faces the problem of lack of memory.

The idea is to keep track of all references to the younger generation without having to go through the entire older generation. In addition, Scavenger points a set of references from the new space to the old space of the object.

Conclusion

Of course, this is just an overview of the GC strategy in the JavaScript universe. The process is much more complex and deserves further reading. The Mozilla GC documentation and V8 garbage collector are highly recommended talk) as additional resources.

Please keep in mind that, as with many other languages, we cannot be sure of GC runs. Since 2019, it depends on the GC, from time to time, to perform cleanups that you cannot trigger yourself.

On top of that, the way you code very much affects how much memory javascript will allocate. That’s why it’s important to understand the peculiarities of garbage collector memory allocation and the strategy to release it. There are several open source lint and hint tools that can help you identify and analyze these leaks and other pitfalls in your code.