Introduction to the

Every programming language has its own memory management mechanism, for example simple C has low-level memory management primitives like malloc() and free(). Similarly, when we learn JavaScript, it is necessary to understand the memory management mechanism of JavaScript. JavaScript’s memory management mechanism is that memory primitives are allocated when variables (objects, strings, and so on) are created and then “automatically” freed when they are no longer in use. The latter is called garbage collection. This “automatic” is confusing and gives JavaScript (and other high-level languages) developers the illusion that they don’t have to worry about memory management. Memory space is not a commonly mentioned concept for front-end development and is easily overlooked. Including myself, of course. For a long time it was thought that the concept of memory space was not that important in JS learning. However, when I went back to rearrange the JS foundation, I found that due to their vague cognition, I did not understand a lot of things. What about basic reference data types and reference passing? For example, what’s the difference between shallow copy and deep copy? There are closures, prototypes, and so on. But in fact, in the process of using JavaScript for development, understanding JavaScript memory mechanism can help developers have a clear understanding of what happens in the process of executing their own code, and can also improve the quality of the project code.

The memory model

The JS memory space is divided into stack, heap, and pool (also generally classified as stack). The stack holds variables, the heap holds complex objects, and the pool holds constants.

Base data type and stack memory

The basic data types in JS, which have fixed sizes, are usually stored in stack memory (except for closures) and allocated by the system automatically. We can directly manipulate the values stored in the stack memory space, so the basic data types are accessed by value. Data is stored and used in the stack memory in a manner similar to the stack data structure in the data structure, following the last in, first out principle. Number String Null – Undefined Boolean Number String Null – Undefined Boolean Number String Null – Undefined Boolean Number String Null – Undefined Boolean

Table tennis box
5
4
3
2
1

The ping-pong balls are stored in the same way that data is accessed on a stack. Ping-pong ball 5, on the top of the box, must be the last to go in, but can be used first. And if we want to use the bottom ping-pong ball 1, we have to take the top four ping-pong balls out and put ping-pong ball 1 on top of the box. This is the characteristics of stack space first in last out, last in first out.

Reference data type and heap memory

Unlike other languages, JS reference data types, such as Array Array, have variable values. Values that reference data types are objects held in heap memory. JS does not allow direct access to locations in heap memory, so we cannot directly manipulate the heap memory space of objects. When you manipulate an object, you’re actually manipulating a reference to the object rather than the actual object. Therefore, values of reference types are accessed by reference. The reference here can be loosely interpreted as an address stored in stack memory that is associated with the actual value of heap memory. Stacks access data in much the same way as bookshelves and books. Although the book is also orderly stored on the shelf, but as long as we know the name of the book, we can easily take out the book we want, and do not have to take out all the table tennis balls from the table tennis box to get a certain table tennis ball in the middle. For example, in JSON format data, we can store key-value unordered, because the difference in order does not affect our use, we only need to care about the name of the book.

To better understand stack memory and heap memory, we can use the following examples and diagrams to understand. var a1 = 0; Var a2 = ‘this is string’; Var a3 = null; Var b = {m: 20}; Var c = [1, 2, 3]; var c = [1, 2, 3]; // The variable c is stored in stack, and [1, 2, 3] is stored in heap memory as an object

The variable name The specific value
c 0x0012ff7d
b 0x0012ff7c
a3 null
a2 this is string
a1 0

[stack memory space] ——->

Heap memory space [1,2,3] {m: 20}Copy the code

So when we want to access the reference data type in the heap, we actually get the address reference (or address pointer) of the object from the stack, and then get the data we need from the heap. Now that we understand the memory space of JS, we can use the memory space properties to verify some of the characteristics of reference types. We often encounter a similar question in front-end interviews

// demo01.js
var a = 20;
var b = a;
b = 30;
// What is the value of a?

// demo02.js
var m = { a: 10.b: 20 };
var n = m;
n.a = 15;
// what is the value of m.a
Copy the code

When data in stack memory is replicated, the system automatically assigns a new value to the new variable. Var b = a; var b = a; var b = a; The details are shown in figure. So if we change the value of B, the value of A doesn’t change.

Stack memory space
a 20

[Before replication]

Stack memory space
b 20
a 20

[After copying]

Stack memory space
b 30
a 20

[Modified b value] This is the illustration of Demo1

In Demo02, we perform a copy of the reference type with var n = m. The copy of the reference type also automatically assigns a new value to the new variable in stack memory, but this new value is only an address pointer to the reference type. When the address Pointers are the same, even though they are independent, the concrete objects accessed in the heap memory are actually the same. | stack memory space | | | | | specific value variable name

m 0x0012ff7d

[Before replication]

Heap memory space
{a:10,b:20}

[Before replication]

Stack memory space
The variable name
m
n

[After copying]

Heap memory space
{a:10,b:20}

[After copying]

This is the demo2 diagram

In addition, we can also use this as a basis to understand JavaScript execution context, scope chain, closure, prototype chain and other important concepts step by step. I’ll do the rest later. I’m so tired just doing this.

Memory life cycle

Memory allocated in the JS environment generally has the following life 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 reclamation: The garbage collection mechanism reclaims unused memory

To make this easier to understand, we use a simple example to explain this cycle.

var a = 20;  // Allocate space in memory for numeric variables
alert(a + 100);  // Use memory
var a = null; // Free up memory space after use
Copy the code

The first and second steps are well understood. JavaScript allocates memory when you define a variable. The third step to free up memory space is a point we need to understand.

Now, what’s the difference between null and undefined in terms of memory?

Typeof (null) //object typeof(undefined) //undefined?

Now think again, what is the declaration cycle of constructors and immediately executed functions?

By the way, const in the ES6 syntax declares a read-only constant. Once declared, the value of a constant cannot be changed. But the following code can change the value of const. Why?

const foo = {}; 
foo.prop = 123;
foo.prop / / 123
foo = {}; // TypeError: "foo" is read-only
Copy the code

Memory recovery

JavaScript has an automatic garbage collection mechanism, so how does this automatic garbage collection mechanism work? It is simply a matter of finding values that are no longer in use and freeing up memory. The garbage collector performs a release operation at regular intervals. In JavaScript, the most commonly used are removed by marking algorithm to find which object is no longer continue to use, thus a = null only actually made a release reference operation, make a corresponding value originally lose reference, from execution environment, the value will be the next time the garbage collector performs operations were found and released. Dereferencing when appropriate is an important way to get better performance for your pages.

  • In a local scope, when the function is finished executing, there is no need for local variables to exist, so it is easy for the garbage collector to make judgments and reclaim. However, it can be difficult to determine when global variables need to automatically free memory space, so we need to avoid using global variables in our development to ensure performance issues.

  • Take Google’s V8 engine, where all JAVASCRIPT objects are allocated via the heap. When we declare a variable in our code and assign a value to it, the V8 engine allocates a portion of the heap memory to the variable. If the allocated memory is insufficient to store the variable, the V8 engine will continue to allocate memory until the heap size reaches the V8 engine’s maximum memory size (by default, the V8 heap size is 1464MB on 64-bit systems and 732MB on 32-bit systems).

  • In addition, the V8 engine performs generational management of JAVASCRIPT objects in heap memory. New generation: A new generation is a JAVASCRIPT object with a short lifetime, such as temporary variables, strings, etc. Old generation: Old generation refers to objects that survive multiple garbage collections and have a long life cycle, such as master controller and server objects.

Please see the following code to analyze the garbage collection.

function fun1() {
    var obj = {name: 'csa'.age: 24};
}
 
function fun2() {
    var obj = {name: 'coder'.age: 2}
    return obj;
}
 
var f1 = fun1();
var f2 = fun2();
Copy the code

In the code above, when var f1 = fun1(); {name:’csa’, age:24}; var f2 = fun2(); The execution environment creates an {name:’coder’, age=2} object, and then releases the memory of {name:’csa’, age:24} object when the next garbage collection comes. This does not free the memory of {name:’coder’, age:2}. This is because the fun2() function returns {name:’coder, age:2′} and assigns its reference to f2. F2 is a global variable, so without unloading the page, The object that f2 points to {name:’coder’, age:2} is not recycled. Due to the peculiarities of the JavaScript language (closures…) How to determine whether an object will be recycled or not becomes extremely difficult, you see.

Garbage collection algorithm

For garbage collection algorithms, the core idea is how to determine that memory is no longer used.

Reference counting algorithm

Those of you who are familiar with or have worked with C know that a reference is nothing more than a pointer to something. For those unfamiliar with the language, you can simply think of a reference as a path from one object to another. Object is a broad term that refers to entities in the JS environment.

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.

Here’s an example:

// Create an object person with two references to the attributes age and name
var person = {
    age: 12.name: 'aaaa'
};

person.name = null; // Although set to null, the name is not recycled because the Person object also has a reference to name

var p = person; 
person = 1;         // The original person object is assigned a value of 1, but since there is a new reference p to the original person object, it will not be reclaimed

p = null;           // The original Person object is no longer referenced and will soon be reclaimed
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, the garbage collector does not collect, resulting in a memory leak.

Here’s another example:

function cycle() {
    var o1 = {};
    var o2 = {};
    o1.a = o2;
    o2.a = o1; 

    return "Cycle reference!"
}

cycle();
Copy the code

Above we declare a cycle equation containing two objects that refer to 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. Because of this serious shortcoming, this algorithm has been superseded in modern browsers by the tag clearing algorithm described below. But don’t assume the problem has gone away, because it’s the algorithm used by the ancestors of Internet Explorer, which still has a large market. When it comes to compatibility, something that seems very common can cause unexpected problems:

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. So what’s the problem here? Notice that the div variable has a reference to the event handler, and the event handler also has a reference to the div! Div variables can be accessed within functions. A sequential reference appears, according to the algorithm described above, this part of memory will inevitably leak oh. Now you see why front-end programmers hate IE? With a lot of bugs and still occupy a large number of market IE is front-end development life enemy! Honey, no buying, no killing.

Mark clearing algorithm

As mentioned above, reference counting algorithms are no longer used in modern browsers. Most of the modern browsers are based on some improved markup clearing algorithm, the general idea is the same.

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.

According to this concept, the above example can be correctly garbage collected (think about it, why?). .

When div and its time handler are no longer reachable from the global object, the garbage collector marks and reclaims both objects.

How to write memory management-friendly JS code?

If you also want to be compatible with older browsers, you need to be aware of circular references in your code. Or simply use a compatible library to help optimize your code.

With modern browsers, the only thing to be careful about is explicitly disconnecting the object that needs to be recycled from the root. Sometimes this connection is not obvious, and this problem is less common because of the robustness of the marker clearing algorithm. The most common memory leaks are usually related to DOM element bindings:

email.message = documentThe createElement method (" div "); displayList.appendChild(email.message);// Remove the DOM element from the displayList later
displayList.removeAllChildren();
Copy the code

The div element has been cleared from the DOM tree, meaning that it is no longer accessible from the root of the DOM tree. Notice, however, that the div element is also bound to an email object. So as long as the email object exists, the div element will remain in memory.

summary

If your references contain only a few JS interactions, then memory management won’t bother you too much. Once you start building large-scale spAs or server and desktop applications, memory leaks should be on your agenda. Don’t settle for writing programs that work, and don’t assume that a machine upgrade will solve everything.

Memory leaks

What is a memory leak

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. If memory that is no longer needed is not released in time, it is called a memory leak. In some languages (such as C) memory must be freed manually, and the programmer is responsible for memory management.

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);
Copy the code

The malloc method is used to allocate memory. After using the malloc method, you must use the free method to free memory. This is cumbersome, so most languages provide automatic memory management to ease the programmer’s burden, called a “garbage collector,” which I’ve already mentioned without further ado.

Identification of memory leaks

How can a memory leak be observed? 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 us to check memory usage in real time.

Browser method

  1. Open the Developer tools and select the Timeline panel
  2. Select Memory in the Capture field 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.

If the footprint is basically flat and near level, there is no memory leak. Otherwise, memory leaks.

Command line method

The command line can use the Process. memoryUsage method provided by Node.

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
Code Segment
Stack(Local Variables, Pointers)
Heap(Objects, Closures)
Used Heap
  • Resident set size (RSS): All memory footprint, including instruction area and stack.
  • heapTotalThe amount of memory occupied by the heap, both used and unused.
  • heapUsed: The part of the heap used.
  • external: memory occupied by C++ objects in the V8 engine.

The heapUsed field is used to determine memory leaks.

WeakMap

As mentioned earlier, it is important to remove references in a timely manner. However, you can’t remember that much, and sometimes you forget, which is why there are so many memory leaks.

It would be nice to have a way to declare at the time of creating a reference which references must be removed manually and which references can be ignored so that the garbage collection mechanism can free up memory when other references are gone. This greatly relieves the programmer’s burden; you just have to remove the main references.

With this in mind, ES6 introduced two new data structures: WeakSet and WeakMap. Their references to values are not counted in the garbage collection mechanism, which is why they have an “Weak” in their name, indicating that they are Weak references.

Let’s take WeakMap as an example to see how it solves memory leaks.

const wm = new WeakMap(a);const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"
Copy the code

In the above code, create a New Weakmap instance. Then, a DOM node is stored in the instance as the key name, and some additional information is stored in WeakMap as the key value. In this case, a WeakMap reference to an Element is a weak reference and is not counted in the garbage collection mechanism.

That is, the DOM node object has a reference count of 1, not 2. At this point, once the reference to the node is removed, its memory is freed by the garbage collection mechanism. The key value pair saved by Weakmap will also disappear automatically.

Basically, if you want to add data to an object and don’t want to interfere with garbage collection, you can use WeakMap.

WeakMap sample

The WeakMap example is hard to demonstrate because you can’t watch the references inside it disappear automatically. At this point, other references are removed, and there is no reference to the key name pointing to WeakMap, so it is impossible to verify whether the key name exists. (For details, see Ruan Yifeng’s memory leak article). over.

Special thanks to:

  • “JavaScript Memory Leak Tutorial” by Ruan Yifeng
  • JavaScript Memory Management by IfCode
  • Articles from Internet bigwigs

Finally, I am sorry to promote the component library I wrote based on the Taro framework: MP-Colorui.

I will be very happy if I can star easily. Thank you.

Click here for documentation

Click here to get the GitHUb address