Do you know how JavaScript manages memory?

Photo by Fredy Jacob on Unsplash

Do you know how JavaScript manages memory?

When I was studying one of the hardest things in programming was memory management. In the vast majority of low-level languages (which you should at least briefly learn if you plan to boost your computer science understanding), memory is managed manually by a developer. In C++ you need to use malloc function to allocate something in memory. Then if you are not using it anymore you need to invoke free function, otherwise your program will use this memory till it runs.

In JavaScript, on the other hand, memory is managed automatically. The developer does not have to think about explicit memory allocation and memory management is taken care of automatically by the JS engine. In this article, I will try to explain how this magic is implemented in the JS engine.

The Heap

In JavaScript, the memory heap is the region of memory where objects are stored. The heap is managed by the garbage collector, which uses a data structure called the "memory block" to manage the memory.

A memory block is a contiguous region of memory that is used to store an object. Each memory block has a header that contains information about the object, such as its size and type. The memory block also contains the actual data of the object. When an object is created, the JavaScript runtime creates a new memory block on the heap and initializes it with the object's data.

As I've mentioned earlier memory is dynamically allocated during runtime. When an object is created, memory is allocated for it on the heap. When an object is no longer used, the garbage collector will mark it as eligible for collection using the "mark and sweep" algorithm.

Mark and sweep algorithm

This algorithm is used by JS engine to identify and free up memory that is no longer used. This is a two-step process: mark and sweep (surprise!).

Mark phase

During this phase, the GC scans through the memory and identifies all objects that are stilll used. Those which are still in use are marked as "live" and memory will not be freed up. The GC is usually starting from the global object and traverses through memory (which from a data structure perspective is a graph), any reachable object is marked as "live" during this phase.

Here is an example of how the mark phase works in practice:

let father = { name: "Odin" };
let son = { name: "Thor" };

father.child = son;

// If you run the mark phase of GC
// both objects will be marked as "live"

In this example, we create two objects father and son and assign son to a property of father. The mark phase of the garbage collector will start with the global object, find father and son and mark them as "live" objects as they are reachable from the global object.

Sweep phase

Once the mark phase is complete, the garbage collector moves on to the sweep phase. During the sweep phase, the garbage collector goes through the memory and frees up any objects that are not marked as "live."

Here is an example of how the sweep phase works in practice:

let father = { name: "Odin" };
let son = { name: "Thor" };

father.child = son;

father.child = null;

// If you run the mark phase of GC
// the memory block conatining son object will be freed up

In this example, we create two objects father and son and assign son to a property of father. Then we remove the reference to son by setting the property to null. At this point, son is no longer reachable and the sweep phase of the garbage collector will go through the memory heap, find the son and free up the memory allocated to it.

Historically JS engines were using reference counting as a mechanism to check if object is "live" or not. But in the browser environment, it was very easy to create a code that will result in a memory leak.

Garbage collection

In JavaScript, garbage collection is typically performed in a separate thread from the main event loop. This means that garbage collection will not block the main event loop and should not cause significant pauses or delays in the program's execution.

However, there are some cases where garbage collection can cause a brief pause in the program's execution. This can happen when the garbage collector needs to perform a full "stop-the-world" collection, which stops all program execution to perform the collection. This type of collection is usually performed when the program's memory usage exceeds a certain threshold.

In addition, if the garbage collection process takes too long, it can cause a delay in the program's execution. This is especially true if the program's memory usage is very high and the garbage collector needs to go through a large amount of memory to free up memory.

To minimize the impact of garbage collection on the program's execution, modern JavaScript engines employ techniques such as concurrent marking and incremental marking. These techniques allow the garbage collector to run in parallel with the JavaScript execution thread, which can significantly reduce the amount of time the JavaScript execution thread is paused.

The frequency at which the garbage collector runs in a JavaScript engine can vary depending on the specific engine and the environment in which it is running. In general, the garbage collector runs periodically in the background, trying to free up memory that is no longer being used.

In some JavaScript engines, the garbage collector runs on a fixed schedule, such as every few seconds or minutes. In other engines, the garbage collector runs more frequently, such as after a certain number of object allocations or deallocations.

Additionally, the garbage collector may also be triggered to run in response to certain events, such as when the program's memory usage exceeds a certain threshold or when the JavaScript execution thread is idle.

In general, the JavaScript engine takes care of the garbage collection process, developers do not have to trigger it manually. However, you can use the global.gc() in Node.js to trigger the garbage collection manually.