For node.js server developers, there is no need to manually create a delete/free operation after creating an object like C/C++ students to GC (garbage collection). Node.js is just like Java. The VM automatically manages memory resources.

But this does not mean to can rest easy, in a development that may be caused by the negligence or bugs in the memory leak is a very serious problem, so as a qualified server r&d engineers, there is necessary to understand the virtual machine is how to use the memory, encounter problems to cope.

V8 Memory limits

Because NodeJS is powered by Chrome V8, it is also limited to the amount of memory it can use (1.4GB for 64-bit and 1.0GB for 32-bit). Therefore, when we request more memory than this threshold, the program will exit due to insufficient heap memory overflow.

When we declare variables and assign values to them in code, the memory of the objects used is allocated in the heap.(the stack is used to store data in Ram, and unlike C++, Js manages the stack and heap automatically, so the programmer can’t set the stack or heap directly.)

If the allocated heap space does not have enough memory to allocate new objects, the allocated heap space continues to be allocated until the size of the heap exceeds V8’s limit. When the V8 limit is exceeded, the project simply collapses.

We can analyze the amount of memory allocated and occupied by the default V8 package:

const v8 = require("v8");
const size = v8.getHeapStatistics();

function fmate(n) {
  let value = ~~(n / 1024 / 1024).toFixed(2);
  return value ? value + "Mb" : 0;
}

for (const iterator in size) {
  console.log(iterator + "= = = = =" + fmate(size[iterator]));
}
Copy the code

Take a look at how these parameters work:

Some good explanations in the GC-heap-STATS package:`total_heap_size`: Number of bytes allocated by V8 to the heap. This may grow if usedHeap needs more.`used_heap_size`: Number of bytes used by the application data`total_heap_size_executable`: The number of bytes of compiled bytecode and JITed code`heap_size_limit`: ** Absolute limit that the heap cannot exceed (default limit or --max_old_space_size)**`total_physical_size:`Commitment to size`does_zap_garbage`: is a0/1Boolean value indicating whether the --zap_code_space option is enabled. This causes V8 to override the heap garbage with bitmode. RSS takes up more space (resident memory set) because it constantly touches all the heap pages, making them less likely to be paged out by the operating system.`total_available_size`: Available heap size`malloced_memory`: Current amount of memory obtained by malloc`peak_malloced_memory`: Peak memory obtained by mallocCopy the code

Node m.js is executed by default

Let’s look at heap_size_limit= around 2.0g.

Run node-max-old-space-size =8000 m.js again

We found that by extensionheap_size_limitandtotal_available_sizeIt has changed with it.

By the way, print process.memoryUsage() for this time;

Test Scenario 1:MAC Apple M1 8GB ram.

// print Print function see below
const total = [];
setInterval(function () {
  total.push(new Array(20 * 1024 * 1024)); // Large memory footprint
  print();
}, 1000);

Copy the code

The result shows 2g instead of 1.4. This also validates the available heap size shown by the default execution of V8 as verified above.

Test Scenario 2:Win10 8G memory i7 x64

Execution result, close to 1g,Copy the code

Test scenario 3: Adding node memory on a MAC address

tal@taldeMacBook-Pro practise % node --max_old_space_size=4096 mv
{"rss":"736.89 MB"."heapTotal":"164.27 MB"."heapUsed":"162.64 MB"."external":"0.90 MB"}
{"rss":"1378.78 MB"."heapTotal":"324.27 MB"."heapUsed":"322.61 MB"."external":"0.97 MB"}
{"rss":"2020.69 MB"."heapTotal":"487.77 MB"."heapUsed":"482.60 MB"."external":"0.97 MB"}
{"rss":"2662.47 MB"."heapTotal":"651.28 MB"."heapUsed":"642.02 MB"."external":"0.96 MB"}
{"rss":"3303.97 MB"."heapTotal":"819.28 MB"."heapUsed":"802.02 MB"."external":"0.96 MB"}
{"rss":"3946.16 MB"."heapTotal":"995.29 MB"."heapUsed":"962.02 MB"."external":"0.96 MB"}
{"rss":"4586.17 MB"."heapTotal":"1155.29 MB"."heapUsed":"1122.02 MB"."external":"0.96 MB"}
{"rss":"5226.19 MB"."heapTotal":"1315.29 MB"."heapUsed":"1282.02 MB"."external":"0.96 MB"}
{"rss":"5026.48 MB"."heapTotal":"1475.30 MB"."heapUsed":"1442.02 MB"."external":"0.96 MB"}
{"rss":"4899.03 MB"."heapTotal":"1635.30 MB"."heapUsed":"1602.03 MB"."external":"0.96 MB"}
{"rss":"4876.44 MB"."heapTotal":"1795.30 MB"."heapUsed":"1762.03 MB"."external":"0.96 MB"}
{"rss":"5288.52 MB"."heapTotal":"1955.31 MB"."heapUsed":"1922.03 MB"."external":"0.96 MB"}
{"rss":"5876.92 MB"."heapTotal":"2115.31 MB"."heapUsed":"2082.03 MB"."external":"0.96 MB"}
{"rss":"9053.05 MB"."heapTotal":"2275.32 MB"."heapUsed":"2242.02 MB"."external":"0.96 MB"}
{"rss":"9693.09 MB"."heapTotal":"2435.32 MB"."heapUsed":"2402.03 MB"."external":"0.96 MB"}
{"rss":"10333.20 MB"."heapTotal":"2595.32 MB"."heapUsed":"2562.12 MB"."external":"0.96 MB"}
{"rss":"10973.25 MB"."heapTotal":"2755.33 MB"."heapUsed":"2722.03 MB"."external":"0.96 MB"}
{"rss":"11613.31 MB"."heapTotal":"2915.33 MB"."heapUsed":"2882.06 MB"."external":"0.96 MB"}
{"rss":"12253.36 MB"."heapTotal":"3075.34 MB"."heapUsed":"3042.03 MB"."external":"0.96 MB"}
{"rss":"12669.56 MB"."heapTotal":"3235.34 MB"."heapUsed":"3202.06 MB"."external":"0.96 MB"}
{"rss":"12505.06 MB"."heapTotal":"3395.34 MB"."heapUsed":"3362.03 MB"."external":"0.96 MB"}
{"rss":"13716.39 MB"."heapTotal":"3555.35 MB"."heapUsed":"3522.03 MB"."external":"0.96 MB"}
{"rss":"13726.69 MB"."heapTotal":"3715.35 MB"."heapUsed":"3682.03 MB"."external":"0.96 MB"}
{"rss":"13426.98 MB"."heapTotal":"3875.36 MB"."heapUsed":"3842.03 MB"."external":"0.96 MB"}
{"rss":"13058.58 MB"."heapTotal":"4035.36 MB"."heapUsed":"4002.06 MB"."external":"0.96 MB"}
{"rss":"15690.78 MB"."heapTotal":"4164.11 MB"."heapUsed":"4161.95 MB"."external":"0.96 MB"}
Copy the code

The GC Nodejs

Node.js is a JavaScript runtime environment based on Chrome V8 engine.

So V8 is the virtual machine used in Node.js, and the GC in Node.js is really V8 GC.

Node.js is similar to V8 as Java is to the JVM, and when Ryan Dahl chose V8 as the virtual machine for Node.js, V8’s performance was already ahead of all other JavaScript virtual machines. It is still the best yet, so when we do Node.js optimizations, the performance will be improved as the version is upgraded.

Node memory API and cases

The process.memoryUsage method is provided in the node.js environment to view the current process memoryUsage in bytes.

  • Resident set size (RSS):RAMThe portion of memory occupied by the process stored in, including the code itself, stack, heap.
  • heapTotal: Total amount of requested memory in the heap.
  • heapUsed: The amount of memory currently used in the heap. We mainly use this field to determine memory leaks.
  • external:V8in-engineC++Memory occupied by objects.

The analysis file text.js is as follows:

    /** * The unit is byte. The format is MB
    const format = function (bytes) {
        return (bytes / 1024 / 1024).toFixed(2) + ' MB';
    };

    /** * Encapsulates the print method to print memory usage */
    const print = function() {
        const memoryUsage = process.memoryUsage();
        console.log(JSON.stringify({
            rss: format(memoryUsage.rss),
            heapTotal: format(memoryUsage.heapTotal),
            heapUsed: format(memoryUsage.heapUsed),
            external: format(memoryUsage.external),
        }));
    }
Copy the code

1: empty function running memory

Perform the node test. Js: {” RSS “:” 93.06 MB “, “heapTotal” : “4.26 MB”, “heapUsed” : “2.63 MB”, “external” : “0.90 MB}” :

Test.js also needs memory, so it can be read as initialization memory.

2: Create an off-heap memory.

As we know, buffer belongs to off-heap memory. For details, please refer to buffer in depth in this article.

Var buf = buffer.alloc (1024 * 1024 * 1024); var buf = buffer.alloc (1024 * 1024); . Create an off-heap memory of 1gb size, execute again:

// File initialization:
{"rss":"93.06 MB"."heapTotal":"4.26 MB"."heapUsed":"2.63 MB"."external":"0.90 MB"}

// Apply for 1gb off-heap memory:
{"rss":"93.52 MB"."heapTotal":"3.76 MB"."heapUsed":"1.87 MB"."external":"1024.89 MB"}
Copy the code

Contrast: : Why does heapTotal decrease? The answer is given below. RSS: increase 0.46 heapTotal: decrease 0.5 heapUsed: decrease 0.76 External: increase 1g

3. Read one33.7 MThe picturemv.jpgFile:

function readFile() {
  let text = fs.readFileSync("./mv.jpg"."utf-8");
  return text;
}

// Add calls
function print(){
   letres = readFile(); . }Copy the code

Analysis:

// File initialization:
{"rss":"93.06 MB"."heapTotal":"4.26 MB"."heapUsed":"2.63 MB"."external":"0.90 MB"}

// Read files into memory
{"rss":"465.73 MB"."heapTotal":"65.14 MB"."heapUsed":"63.53 MB"."external":"33.08 MB"}
Copy the code

Comparison: RSS: add 363.67MB heapTotal: add 60.88MB heapUsed: Add 60.9MB External: add 32.18MB

node GC

Garbage collection refers to the collection of objects that are not referenced in the application. When an object cannot be accessed from the root node, it is a candidate for garbage collection. The root object can be a global object, a local variable, and cannot be accessed from the root node, meaning that it cannot be referenced by any other active object.

V8 also provides two parameters to adjust the size of the memory limit only during the startup phase: adjust the old generation space and the new generation space.

– Max – old – space – size = 2048 – Max – new – space – size = 2048

Of course, bigger memory is not always better. Server resources are expensive, and a small garbage collection of V8 with 1.5GB of heap memory takes about 50 milliseconds or more, which causes JavaScript threads to pause.

Cenozoic and Old age

The absolute majority of application objects will be short-lived, while a few will be long-lived. To take advantage of this, V8 divides the heap into two classes: new generation and old generation. Objects in the new space are very small, ranging from 1-8MB, and garbage collection is also fast. Objects that survive the garbage collection process in the new generation space are promoted to the old generation space.

Cenozoic space

Because garbage is recycled frequently in new Spaces, it must be processed very quickly, using the Scavenge algorithm, which is a replication algorithm. The new space is split into two equally-sized frost-spaces and to-spaces. It works by copying objects that are alive in from space and then moving them into to space or being promoted into old space. Objects that are not alive in from space are released. Swap from space and to space after these copies are done.

The Scavenge algorithm is very fast for small amount of memory garbage recycling, but it has a large space overhead and is acceptable for the new generation of small amounts of memory

Old age space

The Cenozoic space will be promoted to the older space if it meets certain conditions (Scavenge, to space memory ratio). Objects in the older space have been recycled at least once or more so they are more likely to survive. The Scavenge avenge is inefficient by duplicating the exploits and is a waste of space resources. Consequently, mark-sweep and Mark-compact are used in older Spaces.

The application of Mark-sweep is characterized by labeling and scavenging. As opposed to the Scavenge algorithm, which only copies live objects, Mark-sweep iterates through all objects in the heap during the labeling stage because the number of live objects is the majority. At this point, a tag sweep is complete.

Everything seems perfect, but there is still a problem, cleared objects are scattered all over the memory address, resulting in a lot of memory fragmentation

Mark-Compact

In the old generation space, in order to solve the memory fragmentation problem of Mark-Sweep algorithm, Mark-Compact (Mark cleaning algorithm) was introduced. During the working process, the living objects were moved to one end. At this time, the memory space was Compact.

Why is recycling expensive? V8 uses different garbage collection algorithms Scavenge, Mark-sweep, and Mark-Compact. These three garbage collection algorithm can avoid the needs in the application to garbage collection when suspended, after garbage collection complete recovery application logic, for the new generation space due to the fast so the impact is not big, but for the old generation space due to the live objects is more, pause will impact, therefore, V8 has also added incremental markers to reduce pause times.

A memory leak

A Memory Leak refers to a program that fails to release dynamically allocated heap Memory for some reason, resulting in a waste of system Memory, slowing down the program and even crashing the system.

The global variable

Undeclared variables or variables that hang under global are not automatically reclaimed and will remain in memory until the process exits, unless the reference relationship is resolved by delete or reassignment to undefined/null.

closure

Do not use memory as a cache

This is probably the fastest implementation we can think of. In addition, caching is often used in business, but after understanding the memory model and garbage collection mechanism in Node.js, you should be careful when using it. Why? The more keys that are stored in the cache, the more long-lived objects that will not be used in garbage collection.

If you start multiple processes or deploy them on multiple machines, each process will save a copy of the memoryStore. This is obviously a waste of resources. The best way to do this is through Redis.

const memoryStore = new Map(a);exports.getUserToken = function (key) {
    const token = memoryStore.get(key);

    if (token && Date.now() - token.now > 2 * 60) {
        return token;
    }

    const dbToken = db.get(key);
    memoryStore.set(key, {
        now: Date.now(),
        val: dbToken,
    });
    return token;
}

Copy the code

Module private variable memory is permanent

Before loading a module’s code, Node.js wraps it with a function wrapper that ensures that the top-level variables (var, const, let) are module-scoped and not global objects.

Exports objects are held in memory until the process exits. This will result in memory permanent, so it is recommended that references to a module be cached only once in the header reference instead of being loaded each time they are used. Otherwise, memory will increase.

(function(exports.require.module, __filename, __dirname) {
    // The module code is actually here
});
Copy the code

Event repeat listening

The EventEmitter class is used. This class contains an array of listeners. By default, the number of listeners is 10. Limits can also be modified for specific EventEmitter instances using the emitters. SetMaxListeners () method.

Other Matters needing attention

If the timer setInterval is used, use the corresponding clearInterval to clear it, because the setInterval will return a value after execution and will not be released automatically. After each operation, a new array will be created, which will occupy memory. For simple traversal, such as map, forEach can be used instead. These are some details in development, but details often decide success or failure. Each memory leak is also inadvertently caused again and again. Therefore, these points also need our attention.

Manual garbage collection

The heap is used to store object reference types, such as strings and objects. Create a Fruit in the following code and store it in the heap

// example.js
function Quantity(num) {
    if (num) {
        return new Array(num * 1024 * 1024);
    }
    return num;
}

function Fruit(name, quantity) {
    this.name = name
    this.quantity = new Quantity(quantity)
}

let apple = new Fruit('apple');
print();
let banana = new Fruit('banana', 20);
print();
Copy the code

Using the above code, the memory shown below shows that the Apple object heapUsed is only 4.21 MB in use, while banana creates a large array space for its Quantity property causing heapUsed to soar to 164.24 MB

$ node example.js

{"rss":"19.94 MB"."heapTotal":"6.83 MB"."heapUsed":"4.21 MB"."external":"0.01 MB"}
{"rss":"180.04 MB"."heapTotal":"166.84 MB"."heapUsed":"164.24 MB"."external":"0.01 MB"}
Copy the code

Taking a look at the memory usage, the root node holds a reference to each object, so nothing can be freed and GC cannot be done, as shown in the following figure

Suppose the banana object is no longer in use, give it some new value, banana = null, and see what happens?

// example.js
let apple = new Fruit('apple');
print();
let banana = new Fruit('banana'.20);
print();
banana = null;
global.gc();
print();
Copy the code

In the following code, the expose-gc parameter allows the garbage collection mechanism to be performed manually, and the banana object is set to null to gc

$ node --expose-gc example.js
{"rss":"19.95 MB"."heapTotal":"6.83 MB"."heapUsed":"4.21 MB"."external":"0.01 MB"}
{"rss":"180.05 MB"."heapTotal":"166.84 MB"."heapUsed":"164.24 MB"."external":"0.01 MB"}
{"rss":"52.48 MB"."heapTotal":"9.33 MB"."heapUsed":"3.97 MB"."external":"0.01 MB"}
Copy the code

As you can see in the figure below, the banana node on the right has nothing left, and the memory has been freed after GC.

Memory detection tool

  • node-heapdumpDumpV8 heap information is a tool for dumpV8 heap information,
  • node-profileralinodeThe team produced one withnode-heapdumpSimilar tools for capturing heap snapshots,
  • Easy-MonitorLightweight Node.js project kernel performance monitoring + analysis tools,Github.com/hyj1991/eas…
  • Node.js-Troubleshooting-GuideNode.js Application Troubleshooting Guide for online/offline faults, pressure measurement problems, and performance Tuning
  • alinodeThe Node.js Performance Platform is a comprehensive solution that provides Performance monitoring, security alerts, troubleshooting, and Performance optimization services for medium – and large-sized Node.js applications. alinode

Refer to the article

  • Blog.csdn.net/waiting677/…
  • www.imooc.com/article/288…