Unlike C/C++, which allows programmers to create or free memory, JS is similar to Java, which uses its own set of garbage collection algorithms for automatic memory management. As a senior front-end engineer, for JS memory recovery mechanism is the need to be very clear, so that in extreme conditions to analyze the performance of the system bottlenecks, on the other hand, learning The mechanism, but also for our deep understanding of JS closure features, as well as to the efficient use of memory, there are a lot of help.

V8 Memory limits

In other backend languages, such as Java/Go, there are no limitations on memory usage, but unlike JS, V8 can only use a portion of the system’s memory. Specifically, V8 can only allocate a maximum of 1.4 GB on 64-bit systems and 0.7 GB on 32-bit systems. If you think about it on the front end, it’s not a huge memory requirement, but on the back end, if NodeJS encounters a file of more than 2 gigabytes, it won’t be able to read it all into memory for various operations.

We know that for stack memory, when the ESP pointer moves down, i.e. after a context switch, the space at the top of the stack is automatically reclaimed. However, heap memory is more complicated, and we will focus on the garbage collection of heap memory.

All object type data in JS is allocated through the heap. When we construct an object for assignment, the corresponding memory is already allocated to the heap. You can keep creating objects like this and let V8 allocate space for them until the heap reaches its maximum size.

So why does V8 have a memory ceiling? Obviously my machine large dozens of gigabytes of memory, can only let me use so little?

Basically, it is jointly determined by two factors, one is the execution mechanism of JS single thread, the other is the limitation of JS garbage collection mechanism.

First, JS is single-threaded, which means that once garbage is collected, all other running logic is suspended. Garbage collection, on the other hand, is a very time-consuming operation, as described by V8:

Taking 1.5GB of garbage collection heap memory as an example, V8 takes more than 50ms to do a small garbage collection and even more than 1s to do a non-incremental garbage collection.

It can be seen that it takes a long time, and in such a long time, our JS code execution will always have no response, resulting in application lag, resulting in a sharp decline in application performance and responsiveness. Therefore, V8 made a crude choice to limit the heap, which was a tradeoff, since most of the time you wouldn’t have to manipulate several gigabytes of memory.

However, you can adjust the memory limit if you want. The configuration command is as follows:

// This is to adjust the old generation of this part of the memory, in MB. More on new generation and old generation memory later
node --max-old-space-size=2048 xxx.js 
Copy the code

or

// This is to adjust the memory of this part of the new generation, in KB.
node --max-new-space-size=2048 xxx.js
Copy the code

New generation memory reclamation

V8 splits the heap memory into two parts for processing – the new generation and the old generation. As the name implies, the new generation is temporarily allocated memory, which lives for a short time, while the old generation is resident memory, which lives for a long time. V8 heap memory, which is the sum of two memories.

Based on these two different types of heap memory, V8 uses different reclamation strategies that are optimized for different scenarios.

First, the memory of the new generation. I have just introduced the method of adjusting the memory of the new generation. What is its default memory limit? 32MB and 16MB on 64-bit and 32-bit systems, respectively. Small enough, but it makes sense that the variable in the new generation has a short lifetime and is not likely to cause too much memory burden, so it can be made small enough.

So, what’s the next generation of recycling?

First, the memory space of the new generation is divided into two parts:

The From part indicates the memory that is being used, and the To part indicates the memory that is currently idle.

When garbage collection is performed, V8 checks the objects in the From section and copies them To To memory if they are alive (they were placed From scratch in order in To memory) or directly collects non-alive objects.

After all the surviving objects in the From are in the To memory in sequence, the roles of the From and To are reversed, the From is now idle, the To is in use, and so on.

Now, you might ask, why do all of this when you can just recycle nonviable objects?

Note that I specifically stated that the To memory is placed sequentially from the beginning, in order To deal with a scenario like this:

The dark squares represent the living objects and the white parts represent the memory to be allocated. Since the heap is allocated continuously, this scattered space can cause slightly larger objects to be unable to allocate space. This scattered space is also called memory fragmentation. The new generation of garbage recycling algorithms described are also known as the Scavenge algorithm.

The Scavenge algorithm is designed To take care of memory fragments, and the exploiture occurs when the space looks like this:

Is it a lot cleaner? This greatly facilitates the subsequent allocation of continuous space.

The Scavenge algorithm has the advantage of being insane, using only half the amount of new generation memory, but only storing objects with short lifetimes, which are usually very small, so the time performance is very good.

Old generation memory reclamation

Just introduced the recycling method of the new generation, so if the variables in the new generation still exist after multiple recycling, they will be put into the old generation memory, which is called promotion.

There’s more to promotions than that. Let’s take a look at what triggers promotions:

  • Have been Scavenge once.
  • The memory footprint of To (idle) space exceeds 25%.

Insane insane insane insane insane insane insane insane insane insane insane insane

So for old generation, what kind of strategy is adopted for garbage recycling?

The first step is to mark – clean. This process, described in detail in JavaScript Advanced Programming (3rd Edition), is divided into two phases, the marking phase and the cleanup phase. All objects in the heap are first iterated and marked, then variables used in the code environment and strongly referenced variables are unmarked, and the remaining variables are deleted, which are reclaimed in the subsequent cleanup phase.

Of course, this leads to the problem of memory fragmentation, and the space discontinuities of living objects make subsequent space allocation difficult. How did old generation deal with this problem?

Step 2: Defragment memory. V8’s solution is very simple and crude: after the purge phase is over, all surviving objects are moved to one side.

Since it’s a moving object, it’s not going to be fast, and in fact it’s the most time-consuming part of the process.

Incremental tag

Due to the single-thread mechanism of JS, V8 garbage collection will inevitably block the execution of business logic, if the old generation garbage collection task is very heavy, then the time will be terrible, seriously affect the performance of the application. In order to avoid this problem, at this time the V8, the plan of the increment of the tag is a relief complete tag task is divided into many small parts, each finished a small part of the “rest”, js application logic to perform for a while, and then execute the following part, if circulation, until mark phase completed into memory fragments of the above. In fact, this process is similar to the React Fiber process, so I won’t elaborate on it here.

After incremental marking, the garbage collection process blocks JS applications by a factor of 6, which is a very successful improvement.

JS garbage collection principle is introduced here, in fact, it is very simple to understand, it is important to understand why it does it, not just how to do it, I hope this summary can inspire you.

References:

JavaScript Advanced Programming (3rd Edition)

Simple NodeJS by Ling Park

Geek Time “How Browsers Work and Practice”