In the past for a long time, JavaScript developers rarely encounter need to precise control of the scene of memory, is also a lack of control means, when it comes to memory leaks, you might first think of the early in the browser caton, if too much memory footprint, such as basic code for recycling, the user already impatiently refresh the web page.
With the development of Node, the application scenarios of JavaScript are no longer limited to the browser. In the scenarios of short execution in the browser, due to the short running time and running on the user’s machine, memory will be freed as the process exits, so there is little need for memory management. However, as Node becomes more widely used on the server side, the same problems that exist in other languages are increasingly exposed in JavaScript.
We heard about garbage collection when we were studying JavaScript. JavaScript uses garbage collection for automatic memory management, which allows developers to write JavaScript without having to worry about memory allocation and freeing as they do in other languages. Only in the browser for development, because very few meet recycling a performance impact, to project the Node greatly widened the JavaScript application scenarios, when application scenario extend from the browser to various scenarios, we can found that the stand or fall of memory management and garbage collection status are crucial to good, In both the browser environment and node environment, this is all tied to the V8 engine.
1. V8 memory limits
In a normal back-end language, there is no limit to basic memory usage, but when you use memory in JavaScript in Node, you’ll find that you can only use partial memory (about 1.4 GB on 64-bit systems and 0.7 GB on 32-bit systems), and node cannot read a large file directly into memory for processing under this limit. Even if your computer has 16 gigabytes of physical memory, it’s not going to be fully utilized by a single Node process.
This problem is due to the V8 engine, so JavaScript objects used in Node are mostly allocated and managed by V8 itself. This management mechanism is more than adequate in the browser for all front-end page requirements, but in the Node environment, it limits the ability of developers to analyze and process large memory files.
Although operating with large memory on the server side is not a common requirement scenario, we can behave like dancing with shackles when we have this limit, which can cause the process to exit if we accidentally touch it in the real world, or cause the browser to go blank or freeze in the browser environment. Only when you understand how it works can you avoid problems and better manage memory. (This paragraph is from Node in Plain English, I think it’s very well written)
2. V8 object allocation
In V8, JavaScript objects are allocated through heap memory. Node provides a way to view memory usage by entering the following code in the Node environment:
console.log(process.memoryUsage());
Copy the code
Executing the above code yields output memory usage information in bytes:
Among the parameters returned by the memoryUsage method:
- RSS stands for resident set Size and is resident memory for a process
- HeapTotal is the allocated heap memory
- HeapUsed is the amount currently in use
- External represents the memory usage of C++ objects bound to Javascript objects
When we declare variables and assign values in our code, the memory of the objects used is allocated in the heap. If the allocated heap does not have enough free memory to allocate new objects, the heap will continue to be allocated until the heap size exceeds the V8 engine limit.
So why does V8 limit the size of the heap?
- The ostensible reason is that JavaScript initially runs only in a browser environment, where there are few memory-intensive scenarios, so V8’s limitations are more than adequate for web pages
- The underlying reason is V8’s garbage collection mechanism. According to the official version to 1.5 G of garbage collection of heap memory as an example, the V8 to do a small recycling needs more than 50 ms, and make a non incremental recovery even need more than 1 s, see its time consuming, and in this time 1 s, application performance and response time will greatly decreased, the situation not only backend cannot accept, front end also can’t accept it, What’s more, users can’t accept it. So limiting heap memory directly was a good choice in that case.
Of course, this limit is not dead, and V8 gives us a way to manually open the limit, allowing us to use more memory:
On the command line, type the following code: node –v8-options, and then we will see v8 options in the command line window. Here we can see the following options:
When Node starts, we can pass –max-old-space-size or –max-new-space-size to adjust the memory limit, such as:
node --min-semi-space-size=1024 index.js
Set the minimum memory size (MB) for a single half-space in the new generation memorynode --max-semi-space-size=1024 index.js
Set the maximum memory size (MB) for a single half-space in the new generation memorynode --max-old-space-size=2048 index.js
Set the maximum memory size of the old generation, in MB
The above parameters in for environment initialization, once came into effect, will not be able to dynamically change, can only manually adjust, if there are any memory, can use this method to ease restrictions on the manual, so as to avoid page hang or crash caused by memory problems, let us know about the garbage collection strategy, on the premise of limit, A dance with shackles doesn’t have to be ugly. (This paragraph is from Node in Plain English, I think it’s very well written)
3. V8 garbage collection
Before going into the garbage collection mechanism, it’s worth taking a quick look at the various collection algorithms V8 uses
3.1 Garbage collection algorithm
V8’s garbage collection algorithm is mainly based on generational garbage collection mechanism. In the early garbage collection, it was found that no algorithm can be suitable for all scenarios, because in practical application, the life cycle of objects varies, and different algorithms can only play a role for specific situations. Therefore, in modern garbage collection algorithms, garbage collection is divided into different generations according to the survival time of objects, mainly into the new generation and old generation, and then use different algorithms for different generations of memory.
3.1.1 V8 Memory generation
In V8, memory is mainly divided into the new generation and the old generation. Objects in the new generation live for a short time, while objects in the old generation live for a long time (or live in memory), as shown in the figure below:
New generation memory space | Old generation memory space |
V8 heap the overall size of the memory space is the new generation of memory space and the old generation, we mentioned two in front of the command line can be used to set the maximum of the space, it is important to note that the maximum need to specify at startup, therefore, V8 used memory cannot be automatically extended according to the circumstance, when more than limit value in the process of memory allocation, Will cause process error, page stuck, white screen.
3.1.2 The Cenozoic Era (Scavenge)
On a generational basis, the Cenozoic generation is recycled mainly through the Scavenge algorithm, which is implemented using the Cheney algorithm, which is a recycling algorithm based on replication.
- The heap is divided into two parts, each of which is called semispace.
- Of the two Semispace, only one is in use and the other is idle
- A space in use is called From space, and a space in idle state is called To space
- When we allocate objects, we allocate them first in the From space
- When garbage collection begins, the surviving objects in the From space are checked
- Living objects are copied To the To space, and space occupied by non-living objects is freed
- After the replication is complete, the roles of the From space and To space are swapped
In short, in the process of generational garbage collection, the heap memory is recycled by copying the surviving objects between two Semispace Spaces, as shown in the figure below:
New generation memory space | Old generation memory space | ||
Semispace (From) | Semispace (To) | ||
The flow chart is as follows:
- So let’s say we’re at
The From space
Three objects A, B, and C are allocated in - If object A is garbage collected after the main thread task is executed for the first time and no other reference is found, it can be collected
- Objects B and C are still active and therefore copied To the To space for storage
- The following will
The From space
All non-viable objects in the - At this time
The From space
Memory in has been emptied, start andTo the space
Complete a role reversal - When the program main thread is performing the second task, the
The From space
A new object D is allocated in - When the garbage collection task is completed, object D has no other reference, indicating that it can be collected
- Objects B and C are still active and copied to again
To the space
To save - Once again
The From space
All non-viable objects in the - In the end,
The From space
andTo the space
Continue to complete a role switchAs you can see, it can be used at a disadvantage is that the half of heap memory, this is determined by the divided space and the replication mechanism, so cannot be applied to all the scenarios, but as a result of this algorithm only copy live objects, and for some scenarios, accounts for only a few live objects, so it has excellent performance in time efficiency. Scavenge is the quintessential space-for-time algorithm.
Note that:
- The actual heap memory used is the sum of the size of the two Semispace Spaces in the new generation and the memory used in the old generation.
- When an object survives multiple copies, it will be considered as an object with a long life cycle. This object will then be moved to the old generation heap memory and processed by a new algorithm. The process of moving from the new generation to the old generation is called
Object the promotion
.
3.1.3 Object Promotion (New => Old)
In the pure Scavenge algorithm, objects From the From space are copied into the To space, and the two Spaces are flipped. But under generational garbage collection, objects in the From space are checked when they are copied To the To space. Under certain conditions, to move the object on the lifetime to the old generation, that is, to complete the object promotion.
It should be noted that there are two main conditions to meet the promotion of the object:
- Whether the object has experienced once
Scavenge
algorithm To the space
Is the memory usage exceeded25%
The promotion process can be represented by the following flow chart:
StateDiagram -v2 [*] --> To be insane From the exploiture Be Scavenge -> To be insane
After the object is successfully promoted, it will be treated as a long-lived object in the old memory space and processed by the new collection algorithm.
3.1.4 Old generation (Mark clearing & Mark Sorting)
The Scavenge avenge would obviously waste half of the memory as the insane, and was eliminated in the old age due to the large number of survivable objects and the application of the Scavenge avenge, in favor of new algorithms such as Mark-sweep and Mark-compact.
Mark-sweep is divided into two stages: marking and cleaning. The steps are as follows:
- All objects in the heap are traversed in the marking phase
- Then mark the living objects
- In the purge phase, unmarked objects are cleared
The biggest problem with Mark-Sweep is the discontinuous state of memory space after a tag collection. This fragmentation can cause problems for subsequent memory allocation. To solve this problem, Mark-compact was proposed. This algorithm is based on Mark-sweep. The difference is that after an object is marked dead, during the process of cleaning, The live object is moved to one end, and when the move is complete, the memory outside the boundary is cleared directly.
Mark-compact is divided into three stages: marking, cleaning and cleaning. The steps are as follows:
- All objects in the heap are traversed in the marking phase
- Then mark the living objects
- In the purge phase, unmarked objects are cleared
- Defragment memory space to move living objects to one end
- After the move is complete, the memory outside the boundary is cleared directly
The flow chart is as follows:
- Suppose there are eight objects A, B, C, D, E, F, G and H in the old generation:
- In the garbage collection
tag
Stage, mark B, D, F, H as active: - After a round of clearing, the memory space becomes discontinuous:
- In the garbage collection
finishing
Phase to move live objects to one end of heap memory: - Finally, during the garbage collection cleanup phase, reclaim all memory to the right of the live object:The Mark-sweep algorithm knows whether an object should be reclaimed by determining whether it can be accessed. As you can see, Scavenge only copies living objects and mark-sweep only cleans dead objects, so the living objects take up a small portion of the new generation memory and the dead objects take up a small portion of the old generation memory, which is why both recycling methods are so efficient.
This completes the entire process of an old generation garbage collection. The following table is a simple comparison of the three garbage collection algorithms:
Recovery algorithm | Mark-Sweep | Mark-Compact | Scavenge |
---|---|---|---|
speed | medium | The slowest | The fastest |
space | Few (with fragments) | Less (no fragments) | Double space (no debris) |
Whether to move objects | no | is | is |
As can be seen from the above table, Mark Sweep does not need to move objects, and the other two algorithms need to move objects, so the execution speed of these two algorithms is not as fast as Mark Sweep. Therefore, V8 mainly uses Mark-sweep in the choice. Use Mark-compact when there is not enough space to allocate promoted objects.
3.1.5 Garbage collection summary
From the perspective of the V8 garbage collection mechanism, it is reasonable for the new generation to have a small memory space, while the old generation has too much space for garbage collection. V8’s memory limitations are more than sufficient for browser pages; For back-end servers, it can also meet most scenarios, and does not affect the use of normal scenarios. However, for the characteristics of garbage collection and the single-threaded execution of JavaScript, garbage collection is one of the factors affecting performance. In order to improve the performance of the application, it is necessary to keep garbage collection as little as possible.
3.2 Avoiding memory Leaks
3.2.1 Use closures less
We know that objects in the scope chain can only be accessed up, so that the outside can’t access the inside. In JavaScript, methods that allow the outside scope to access variables in the inside scope are called closures, thanks to the nature of higher-order functions: functions can be arguments or return values
function foo() {
function bar() {
var local = "Local variable";
return function() {
return local;
};
};
var baz = bar();
console.log(baz());
};
Copy the code
In general, local is reclaimed when the scope is destroyed after the bar() function is executed, but it returns an anonymous function that has access to local. If you want to access local externally, you only need to do a little work with the intermediate function.
JavaScript closures are the advanced features, you can use it to create many wonderful effect, but it’s problem is that once you have a variable reference among the function, the intermediate function will not be released, at the same time also can make the original is not released, the scope of the scope of memory will not be released, unless there is no reference, will be gradually released.
3.2.2 Create fewer global variables
var a = 1;
/ / equivalent to the
window.a = 1;
Copy the code
function foo() {
a = 1;
}
/ / equivalent to the
function foo() {
window.a = 1;
}
Copy the code
function foo() {
this.a = 1;
}
/ / equivalent to the
function foo() {
window.a = 1;
}
Copy the code
In ES5, when a variable is created in a global scope in the form of a var declaration, or when a variable is created in a function scope without any declaration, it is implicitly mounted to the Window global object. During garbage collection, since the Window object can be used as the root node during the tagging phase, properties mounted on the window can be accessed and marked as active resident memory, thus not being garbage collected. The global scope is destroyed only when the entire process exits. If you encounter scenarios where you must use global variables, be sure to set the global variable to NULL after it is used to trigger the recycle mechanism.
3.2.3 Manually Clearing the timer
In our applications, we often use setTimeout or setInterval timers. The timer itself is a very useful function. However, if we are careless and forget to manually clear the timer at an appropriate time, it may lead to memory leakage. Manual clearing:
/ / in vue
created() {
this.id = setInterval(cb, 500);
},
beforeDestroy() {
clearInterval(this.id);
},
/ / in the React
useEffect(() = > {
const id = setInterval(cb, 500);
return () = > clearInterval(id); } []);Copy the code
3.2.4 Manually clearing event Listeners
The removeEventListener() method is used to remove event handlers added by the addEventListener() method to reduce memory leaks and improve application performance:
/ / in vue
created() {
document.addEventListener('click'.e= > cb(e));
},
beforeDestroy() {
document.removeEventListener('click'.e= > cb(e));
},
/ / in the React
useEffect(() = > {
document.addEventListener('click'.e= > cb(e));
return () = > document.removeEventListener('click'.e= >cb(e)); } []);Copy the code
3.2.5 Develop the habit of clearing logs
In addition to the fact that a large number of console. logs can cause memory leaks if we are careless with the previous examples, most non-essential console should be removed in a production environment, otherwise it will cost performance. It’s okay if you call it once or twice, but it’s too much if you put it in a loop, right
4. To summarize
Node extends the main context of JavaScript from the browser to the server, and the details are not the same as those on the browser side. In general, the internal memory of Node is limited and can not be used as much as you like, but it is not completely bad at it. This article mainly refers to the book Nodejs In Plain English. I explained Node memory allocation and V8 engine garbage collection mechanism from different aspects, theoretical knowledge is more, although the daily business may not be used, but I believe it is helpful for everyone’s interview (I was asked last time), in addition, because the source code of V8 engine is implemented in C++, So I haven’t done any in-depth research here (mainly because I don’t know C++). Finally, thanks for reading, and if there are any mistakes in this article, please correct them in the comments section.