I’ve recently been fascinated by Node exploration and am interested in Node for back-end development. So in the spirit of exploration, I reread the memory section of Node.js and realized that I didn’t really care about it at first, which has really opened my eyes and benefited me a lot.


01 preface

With the development of Node, the running of JavaScript is no longer limited to the browser, and the application of Node on the server side makes more and more problems appear. For developers new to JavaScript, memory allocation or memory leaks are rarely a concern. To keep up with the trend, let’s explain how Node uses memory efficiently and effectively to prevent memory leaks.

02 V8 garbage collection and memory limits

JavaScript programming doesn’t have to worry about memory allocation and release because it has a garbage collection mechanism to manage it. But we know very little further about memory management, how memory is allocated and how garbage collection works.

The Node with the V8

Let’s see what the relationship is. First, V8 is a browser engine developed by Google that performs so well that it makes it possible to write server programs in JavaScript. V8 was developed by Lars Bak, a virtual machine expert who had previously focused on high-performance virtual machine development.

Node is a JavaScript runtime platform built on Top of Chrome, so Node’s high performance is like a lap on V8’s lap, and it can enjoy better performance as V8 upgrades.

V8 memory limits

Node’s ability to use memory through JavaScript is limited, and Node cannot directly manipulate large memory objects. This is about 1.4GB on 64-bit systems and 0.7GB on 32-bit systems. The reason is that Node is built on V8, and its memory allocation and management are controlled by V8. While this allocation mechanism is fine in browsers, it is problematic in Node.

V8 object allocation

In V8, all JavaScript objects are allocated by the heap, and the amount of memory an object occupies is uncertain.

When we write some variables in our code, we ask for memory space in the heap. We mentioned above that V8 has memory allocation limitations because of V8’s garbage collection mechanism. Imagine if you had 1.5GB of garbage waiting to be collected, and V8’s garbage collection mechanism took more than 50 milliseconds to run, during which time the program would not run.

If I had to turn this on, I could have changed the parameters during Node startup:

Node --max-old-space-size=1700 xxx.js // Unit: MB Node --max-new-space-size=1024 xxx.js // unit: KBCopy the code

The code above means that the new generation and old generation space changes, later parse what is new generation and old generation space.

V8 garbage collection mechanism

In V8, we know that memory space can be divided into new generation and old generation. Cenozoic space mainly stores some objects with short survival time, while old generation space mainly stores some objects with long survival time.

As mentioned earlier, 64-bit systems can only use about 1.4GB of space and 32-bit systems can only use about 0.7GB of space. This space includes Cenozoic and old generation space, and the old generation space is more than the new generation.


V8 mainly uses two different algorithms, one for the new generation and the other for the old generation, because the relationship between the two is different. The applications are the Scavenge algorithm and the Mark-sweep & Mark-Compact algorithm. The characteristics of the New old age are as follows:

Cenozoic: Fewer surviving objects

Old generation: live object more

1) Scavenge algorithm

  • The new generation of space is divided into two parts, From space and To space
  • Memory is allocated to the From space first, and garbage collection checks for the SURVIVAL of the From space
  • Move the living object From the From space To the From space, free From the From space
  • Swap From and To Spaces

This completes a cleanup of the garbage collection.

(2) Mark – Sweep algorithm

  • Iterate over all objects in the heap, marking those that survive
  • Clears unmarked objects

The Scavenge avenge takes a relatively long time and wastes half the space to store objects, which is inefficient because of the large number of surviving objects in the old space. In general, the Scavenge algorithm replicates living objects, while the Mark-sweep algorithm purges dead ones.

(3) Mark – Compact algorithm

We see that the above algorithm generates fragmentary memory space after a single collectionIf at this time come in a relatively large memory object can not complete allocation, trigger a new round of garbage collection mechanism in advance. So based on this problem, on the basis of the original algorithm, adoptCompression, in the process of marking live objects, objects are moved to the side. Then the border is cleared directly after the marking is complete.

(4) Incremental Marking algorithm

Unlike the previous three, this algorithm ** uses the “step” way **, because the garbage collection mechanism run a time is also some, for the garbage collection mechanism run time is too long, then the page application logic has to stop and wait, this impact is also relatively large.

This is not a concern for the new generation, because there are fewer living objects and the memory footprint is smaller. However, for older generations, “more objects are bigger” and pauses are more significant, so the application logic must be less affected by garbage collection.

Therefore, a “step” approach is adopted, garbage collection mechanism and logical code is executed in segments **, to alleviate the problem of application logic execution caused by long pauses.

03 Memory Usage

We have to talk about scope when it comes to memory. In the code we write, there are only a few areas that can be scoped: functions, with, and global scopes.

var A = function(){
  var user = {}
};
A();
Copy the code

When we execute this function, we create a function scope, as well as a local variable user. The user can only be used in the scope of the function. After the function is finished, the scope is destroyed and the object loses its reference. The referenced object will be released in the next garbage collection. The user is a small object that will be assigned to the new generation’s FROM space.

We know that scopes have the concept of a scope chain, which means that if a variable cannot be found in the current scope, it will look for the parent scope and spread out, throwing undefined errors if it cannot find any.

Summary:

  • Closures that are not released in time can cause memory leaks. Common examples areThe timer
  • The global variable, this variable is released only after the process exits, and the referenced object is released inPermanent memory(The old generation)

V8 heap memory related metrics

We can call process.memoryusage () to check the memoryUsage of Node processes.

HeapTotal (total requested memory in the heap) and heapUsed (used memory in the heap) represent V8’s memory usage. External represents the memory usage of C++ objects bound to Javascript that are managed by V8. RSS is the resident size, the amount of physical memory allocated to the process (as a percentage of the total allocated memory), and contains all C++ and JavaScript objects and code.

We said that the old generation memory space is around 1400MB, to verify the cost of a wave of memory leaks, to see how memory is used up. We manually construct a global object and place it in resident memory, that is, in the old generation.

function showMemory(j) {
  console.log(` this is the first${j+1}Runs `)
  var mem = process.memoryUsage();

  function format(bytes) {
    return (bytes / 1024 / 1024).toFixed(2) + "MB";
  }

  console.log(
    'Process: total requested memory heapTotal:${format(mem.heapTotal)}, currently the heap uses memory heapUsed:${format( mem.heapUsed )}, resident memory RSS:${format(mem.rss)}`
  );
  console.log(
    "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="
  );
}

// Man-eating function
function useMem() {
  var size = 20 * 1024 * 1024;
  var arr = new Array(size);
  for (var i = 0; i < size; i++) {
    arr[i] = 0;
  }
  return arr;
}

var total = [];
 for (var j = 0; j < 20; j++) {// Run 20 times, but you won't actually reach it, because you'll run out of memory showMemory(j); total.push(useMem()); }  showMemory(j) Copy the code

The results were amazing, running out of memory in less than 10 runs. You can imagine how scary it would be if you had a memory leak that would eventually cause the entire process to quit. Here we can clearly see that the old generation space is about 1400MB, because it would be out of range if executed again.


Out of memory

Above we tested the memory in the heap, but we found that the total memory requested was only 1290.33MB, but the heap occupied 1300.81MB, so where is the extra memory? That’s right, it’s actually off-heap memory. This part of the memory is not controlled by V8.

Let’s change the man-eating function above:

function useMem() {
  var size = 200 * 1024 * 1024;
  var buffer = new Buffer(size);
  for (var i = 0; i < size; i++) {
    buffer[i] = 0;
  }
  return buffer;
}
Copy the code

We found that the loop ran successfully 21 times, and we saw that the resident memory had exceeded the V8 limit. The buffer objects are not controlled and allocated by V8 and belong to off-heap memory.

Node’s memory is mostly allocated by V8 and Node’s own allocation. But it is mainly V8 heap memory that is limited by V8 garbage collection.

04 summary

Node extends JavaScript’s main application to the server side, so the details we consider are different from those of the browser. More importantly, the allocation of memory resources is a problem, which may cause some memory leaks, so that the garbage collection mechanism can not clean up and release memory, which may cause the server to crash.

Be careful not to write global variables or use closures too often, which can lead to memory leaks if not freed properly. The nature of a memory leak is that objects that should be recycled are not recycled (moved to old space).


Reference Books:

  • Node.js is easy to understand