preface

Low-level languages like C typically have low-level memory management interfaces, such as malloc() and free() for allocating and freeing memory. With JavaScript, memory is allocated when variables (objects, strings, etc.) are created and “automatically” freed when they are no longer used, a process called garbage collection. Because of the automatic garbage collection mechanism, most Javascript developers feel they can ignore memory management, which can lead to memory leaks in some cases.

If we run out of memory, the program will consume more and more memory, eventually causing the client to stall or even become unresponsive. If we use Node.js as the back-end application, because the back-end application will run for a long time, if there is memory overflow, the consequences will be more serious, the server memory may be used up quickly, the application can not run properly.

Memory life cycle

Memory allocated in the JS environment has the following declaration cycle:

  1. Memory allocation: When we declare variables, functions, and objects, the system automatically allocates memory for them
  2. Memory usage: reading and writing memory, that is, using variables, functions, etc
  3. Memory collection: After use, the garbage collection mechanism will automatically reclaim the memory allocation of no longer used memory ### JS

To spare programmers the trouble of allocating memory, JavaScript does so when variables are defined.

var n = 123; // Allocate memory for numeric variables
var s = "azerty"; // Allocate memory to the string

var o = {
  a: 1.b: null
}; // Allocate memory for objects and their contained values

// Allocate memory for arrays and their values (just like objects)
var a = [1.null."abra"]; 

function f(a){
  return a + 2;
} // Allocate memory for functions (callable objects)

// Function expressions can also assign an object
someElement.addEventListener('click'.function(){
  someElement.style.backgroundColor = 'blue';
}, false);
Copy the code

Some function calls result in allocating object memory:

var d = new Date(a);// Assign a Date object

var e = document.createElement('div'); // Assign a DOM element
Copy the code

Some methods allocate new variables or new objects:

var s = "azerty";
var s2 = s.substr(0.3); // s2 is a new string
// Since strings are invariants,
// JavaScript may decide not to allocate memory,
// only the range [0-3] is stored.

var a = ["ouais ouais"."nan nan"];
var a2 = ["generation"."nan nan"];
var a3 = a.concat(a2); 
// The new array has four elements, which are the result of a joining a2
Copy the code

JS memory usage

The process of working with values is actually reading and writing to allocated memory. Reading and writing may be writing the value of a variable or an object property, or even passing the parameters of a function.

var a = 10; // Allocate memory console.log(a); // The memory usage copies the codeCopy the code

JS memory reclamation

JS has automatic garbage collection mechanism, so this automatic garbage collection mechanism principle is what? It is simply a matter of finding values that are no longer in use and freeing up memory.

Most memory management problems occur at this stage. The hardest task here is to find variables that are no longer needed.

Variables that are no longer needed, i.e. end-of-life variables, are local variables. Local variables exist only during the execution of a function. When the function is finished and there are no other references (closures), the variable is marked for recycling.

The lifetime of global variables does not end until the browser unloads the page, meaning that global variables are not garbage collected.

Because of the automatic garbage collection mechanism, the developer can not care about or pay attention to the problem of memory release, but the release of useless memory is an objective thing. Unfortunately, the latest garbage collection algorithms cannot intelligently recycle all extreme cases, even without considering the performance impact of garbage collection.

Let’s explore the JS garbage collection mechanism.

The garbage collection

reference

Garbage collection algorithms rely heavily on the concept of references.

In a memory-managed environment, an object that has access to another object (implicitly or explicitly) is said to reference another object.

For example, a Javascript object has references to its stereotype (implicit references) and to its attributes (explicit references).

Here, the concept of “object” refers not only specifically to JavaScript objects, but also to function scopes (or global lexical scopes).

Reference counting garbage collection

This is the most rudimentary garbage collection algorithm.

Reference counting algorithms define “memory out of use” simply by looking at whether an object has a reference to it. If no other object points to it, it is no longer needed.

var o = { 
  a: {
    b:2}};// Two objects are created, one referenced as an attribute of the other, and the other assigned to the variable o
// Apparently, none of them can be garbage collected


var o2 = o; The o2 variable is the second reference to "this object"

o = 1;      // The original reference o to "this object" is now replaced by O2

var oa = o2.a; // Reference the a attribute of "this object"
// Now "this object" has two references, one to O2 and one to oa

o2 = "yo"; // The original object is now zero references
           // He can be recycled
           // However, the object of its property A is still referenced by OA, so it cannot be reclaimed yet

oa = null; // The object with the a attribute now has zero references
           // It can be garbage collected
Copy the code

As can be seen from the above, reference counting algorithm is a simple and effective algorithm. But it has a fatal problem: circular references.

If two objects refer to each other, even though they are no longer in use, garbage collection does not collect, resulting in a memory leak.

Consider an example of a circular reference:

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o references o2
  o2.a = o; // o2 references o here

  return "azerty";
}

f();
Copy the code

Above we declare a function f that contains two objects that reference each other. At the end of the call to the function, objects o1 and O2 are actually out of the scope of the function and are therefore no longer needed. However, according to the principle of reference counting, their references to each other still exist, so this part of memory will not be reclaimed, and memory leakage is inevitable. Here’s another practical example:

var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};
Copy the code

The JS notation above is perfectly normal, creating a DOM element and binding it to a click event. The div variable has a reference to the event handler, and the event handler has a reference to the div! Div variables can be accessed within functions. A sequential reference appears, and according to the algorithm described above, this part of memory is inevitably leaked.

In order to solve the problem of circular references, modern browsers implement garbage collection by using tag clearing algorithms.

Mark clearing algorithm

The tag clearing algorithm defines “no longer used object” as “unreachable object”. In simple terms, it periodically scans objects in memory from the root (in JS, global objects). Anything that can be reached from the root is still needed. Objects that cannot be reached from the root are marked as unused and recycled later.

As you can see from this concept, untouchable objects include the concept of unreferenced objects (objects without any references are also untouchable objects). But the reverse is not necessarily true.

Workflow:

  1. The garbage collector will mark all variables stored in memory at runtime.
  2. Start at the root and clear the marks of objects that can be touched.
  3. Variables that still have tags are considered to be ready for deletion.
  4. Finally, the garbage collector performs the final step of memory cleaning, destroying the tagged values and reclaiming the memory they occupy.

Circular references are no longer a problem

Look again at the circular reference example:

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o references o2
  o2.a = o; // o2 references o

  return "azerty";
}

f();
Copy the code

After the function call returns, the objects referenced by both loops can no longer be retrieved from the global object at garbage collection time. Therefore, they will be collected by the garbage collector.

A memory leak

What is a memory leak

The program needs memory to run. The operating system or runtime must supply memory whenever the program requests it.

For continuously running daemons, memory that is no longer needed must be released in a timely manner. Otherwise, the memory footprint increases, which can affect system performance at best or cause process crashes at worst.

Essentially, a memory leak is a waste of memory caused by an oversight or error that prevents a program from releasing memory that is no longer in use.

Identification of memory leaks

As a rule of thumb, you have a memory leak if your footprint is larger than the previous one after five consecutive garbage collections. This requires a real-time view of memory usage.

In Chrome, we can check memory usage like this

  1. Open developer tools and select the Performance panel
  2. Check Memory at the top
  3. Click the Record button in the upper left corner
  4. Various operations are carried out on the page to simulate the use of users
  5. After a period of time, click the Stop button in the dialog box, and the memory usage for that period will be displayed on the panel

Take a look at an illustration:

There are two ways to determine if there is currently a memory leak:

  1. After multiple snapshots are taken, compare the memory usage of each snapshot. If the memory usage increases, a memory leak occurs
  2. After a snapshot is taken, you can view the memory usage trend. If the trend is not steady, a memory leak occurs

Use the process.memoryUsage method provided by Node in the server environment to view memoryUsage

console.log(process.memoryUsage());
/ / {
// rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772
// }
Copy the code

Process. memoryUsage returns an object containing memoryUsage information for Node processes.

This object contains four fields, in bytes, with the following meanings:

  • Resident set size (RSS) : All memory usage, including instruction area and stack.
  • HeapTotal: The amount of memory occupied by the heap, both used and unused.
  • HeapUsed: part of the heapUsed.
  • External: memory occupied by C++ objects inside the V8 engine.

The heapUsed field is used to determine memory leaks.

Common memory leak cases

Unexpected global variables

function foo() {
    bar1 = 'some text'; // No declared variable is actually a global variable => window.bar1
    this.bar2 = 'some text' // Global variable => window.bar2
}
foo();
Copy the code

In this example, two global variables bar1 and bar2 are accidentally created

Forgotten timers and callback functions

In many libraries, if observer mode is used, callback methods are provided to invoke callback functions. Remember to recycle these callbacks. Here’s an example of setInterval:

ar serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData); }},5000); // call every 5 seconds
Copy the code

If the subsequent renderer element is removed, the entire timer actually does nothing. However, if you do not recycle the timer, the entire timer is still valid. Not only can the timer not be recycled, but also the dependencies in the timer function can not be recycled. ServerData in this case also cannot be recycled.

closure

In JS development, we often use closures, an inner function that has access to variables in the outer function that contains it. Closures can also leak memory in the following cases:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) // Reference to 'originalThing'
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join(The '*'),
    someMethod: function () {
      console.log("message"); }}; };setInterval(replaceThing, 1000);
Copy the code

This code, each time replaceThing is called, theThing gets an object containing a large array and an object for the new closure someMethod. Unused is also a closure that references originalThing.

The key to this example is that closures are shared in scope, and while unused may never be called, someMethod may be called, making it impossible to reclaim its memory. As this code is executed repeatedly, memory continues to grow.

DOM references

Most of the time, when we operate on the Dom, we store references to the Dom in an array or Map.

var elements = {
    image: document.getElementById('image')};function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // At this point we still have a reference to #image, the image element, which is still not retrievable.
}
Copy the code

In the above case, even though we removed the image element, there was still a reference to the image element, which still could not be aligned for memory reclamation.

Another point to note is the reference to the leaf node of a Dom tree. For example, if we reference a TD element in a table, once we delete the entire table in the Dom, we intuitively think that memory reclamation should reclaim all elements except the referenced TD element. But in fact, the TD element is a child of the entire table and retains a reference to its parent element. This will result in no memory reclamation for the entire table. So we need to be careful with references to Dom elements.

How do I avoid memory leaks

Remember one rule: return what you don’t use.

  1. Reduce unnecessary global variables and use strict patterns to avoid accidentally creating global variables.
  2. Dereference (variables in closures, DOM references, timer cleanup) when you’re done with the data.
  3. Organize your logic to avoid endless loops that cause your browser to freeze and crash.

V8 memory management

V8 is memory limited because it was originally designed for browsers and is unlikely to encounter large memory usage scenarios. The key reason is that garbage collection causes threads to pause for too long. According to the official statement, a small V8 garbage collection takes 50ms with 1.5 gigabytes of memory, and a non-incremental, or full, garbage collection takes even more than a second. This is clearly unacceptable. So V8 limits the size of memory usage, but node.js can be modified by configuration. It is better to use Buffer objects, because Buffer memory is allocated by the underlying C++ and does not occupy js memory, so it is not restricted by V8.

V8 uses a generational recycling strategy, dividing memory into two generations: the new generation and the old generation

The new generation

The garbage collection in the new generation of memory is mainly implemented by Scavenge algorithm and Cheney algorithm. The new generation of heap memory is divided into multiple Semispace, each Semispace is divided into two parts: from and to. Only the from space is in use. When allocating object space, only the FROM space is allocated. Follow these steps for garbage collection:

2. Copy all living objects to to 3. Reverse from and to, where from is alive and to is dead. Recycle all of toCopy the code

You can see that in the new generation we copy the living objects, the dead objects are left where they are, and they are all recycled. This is because for most of the new variables, it’s probably just for use and soon needs to be released, so in the new generation each recycling will find that a few are alive and a lot are dead. So we’re just copying a few objects, which is more efficient. If a variable survives several iterations in the new generation, it is likely to have a long life cycle and be promoted to the old generation. There are two situations in which people are promoted:

1. In the process of Cenozoic garbage collection, when an object survives multiple copies and is moved to the old generation; 2. In the process of reversing from and to, if the usage of to space exceeds 25%, then all objects from are promoted to the old generationCopy the code

The old generation

The old generation stores objects with a long life cycle, and its structure is a continuous structure, unlike the new generation, which is divided into from and to. There are two ways to collect old garbage: tag sweep and tag merge.

Mark clear

Mark clear marks dead objects and frees their space directly. The memory space becomes discontinuous after the dead object is removed by the tag cleanup method, so another solution emerges: tag merge.

Mark merger

This scheme is a bit like the Cheney algorithm of the new generation, moving the surviving objects to one side, moving the objects that need to be collected to the other side, and then doing an overall garbage collection of the object areas that need to be collected.

Compared with the new generation algorithm, the old generation mainly operates the dead objects, because the old generation is the object with a long life cycle, and the number of dead objects is relatively small. However, the surviving objects of the new generation are mainly operated, because the new generation is the object with a short life cycle, and there are fewer surviving objects for each recycling. In this way, both the new generation and the old generation can handle as few objects as possible in each recycling, which increases efficiency.

provenance

Juejin. Cn/post / 684490… Juejin. Cn/post / 684490…