JS talks about garbage collection mechanism from memory space

 One ❀ cited

Students who are engaged in computer-related technical work have heard a lot about the concept of memory space. After all, people like me who are not from computer science can say a few words about stacks and garbage collection; when I understand the basic types of JS and The storage methods of reference type data are different, so it suddenly dawns on why deep copy should be used. Just knowing and in-depth understanding are two different things, so this article starts from the memory space.

 2❀ Stack, Heap and Queue

Unlike the underlying language of C language, JavaScript does not provide a memory management interface, but automatically allocates memory when variables are created, and automatically releases them when they are no longer needed, which is what we often call the garbage collection mechanism.

But no matter what programming language it is, the life cycle of memory satisfies the following three stages:

a. Allocate the memory space you need

b. Use the allocated memory (read, write)

c. Release or return it when not needed

Most languages ​​are clear about the second step, but for JavaScript, the three steps are implicit, and it is precisely because of this that JavaScript developers have the illusion that they don't need to care about memory management.

The JavaScript memory space is divided into stack, heap, pool, and queue. Among them, the stack stores variables, basic type data and reference pointers to complex type data; the heap stores complex type data; the pool, also known as the constant pool, is used to store constants; and the queue is also used in the task queue. Let's go into detail.

1. Stack data structure

The stack data structure has the characteristics of FILO (first in last out). The more classic one is the table tennis box structure, and the table tennis balls put in first can only be taken out last.  mentioned in an article Understanding JS Execution Context that the execution context stack is used to store all the contexts created during the execution of js code, and it also has the characteristics of FILO.

 Image Source

Data types in js are generally classified into basic data types (Number Boolean Null Undefined String Symbol) and reference data types (Object Array Function...), where the stack is generally used to store basic types of data, for example, the following codes are distributed in the stack memory:

var a = 1;
var b = a;
a = 2;

It can be seen that the variable name and value of the basic type of data are stored in the stack memory. When we copy the variable a to b, the stack will open a new memory to store the variable b, and when we modify the variable a, the variable b will not be changed. Will have any impact, because a and b are two pieces of data that are not related to each other.

2. Heap data structure

The heap data structure is an unordered tree structure, and it also satisfies the storage method of key-value key-value pairs; we only need to know the key name to find the corresponding value through the key. The more classic example is the example of storing books on the bookshelf. We know the title of the book and we can find the corresponding book.

 Image Source

In js, the heap memory is generally used to store reference type data. It should be noted that since the reference type data can generally be expanded and the data size is variable, it is stored in the heap memory; but the reference address of the reference type data is fixed. , so the address pointer will still be stored in the stack memory.

We simulate the following code with a memory map:

var a = [1,2,3];
var b = a;
a.push(4);

When we create an array a, only the variable a and the address pointer pointing to the array in the heap memory are saved in the stack memory, and when we copy a to the variable b, in fact, only a copy of the address pointer is copied, and the two still point to the same Arrays, no matter who modifies them, affect each other.

This is the shallow copy we are familiar with. If you want to have a deeper understanding of shallow copy and deep copy, welcome to read  the article about the difference between deep copy and shallow copy and several ways to realize deep copy .

3. Queue

The queue has the characteristics of FIFO (First In First Out). Unlike the stack memory, there is only one exit in the stack memory for data to be pushed in and out of the stack; while the queue has one entry and one exit, it is more practical to understand the queue An example of this is like we line up to pick up meals, the first to line up will always get the meal first.

 Image Source

The most prominent use of queues in js is the event loop event loop in the js execution mechanism. If you are interested in the js event execution mechanism, you can read the detailed explanation of the blogger's JS execution mechanism, the real meaning of the timer  interval . will get you something.

 Three ❀ garbage collection mechanism

We have already mentioned that JS memory allocation and recycling are automatically completed by the computer, and we also mentioned the concept of garbage collection mechanism, so let’s go into details here.

1. Memory recovery in js

In js, the garbage collector will find out the data that is no longer used every once in a while, and release the memory space it occupies.

In terms of global variables and local variables, the local variables in the function are no longer needed after the function execution ends, so the garbage collector will identify and release them. For global variables, it is difficult for the garbage collector to judge when these variables are not needed, so use global variables as little as possible.

2. Two modes of garbage collection

So how does the garbage collector detect whether a variable is needed? It is roughly divided into two detection methods, reference counting and mark removal.

reference count

The judgment principle of reference counting is very simple. It is to see whether a piece of data still has references pointing to it. If there is no object pointing to it, then the garbage collector will recycle it. For example:

// 创建一个对象,由变量o指向这个对象的两个属性
var o = {
    name: '听风是风',
    handsome: true
};
// name虽然设置为了null,但o依旧有name属性的引用
o.name = null;
var s = o;
// 我们修改并释放了o对于对象的引用,但变量s依旧存在引用
o = null;
// 变量s也不再引用,对象很快会被垃圾回收器释放
s = null;

There is a big problem with reference counting, which is circular references between objects. For example, in the following code, objects o1 and o2 refer to each other. Even if the function is executed, the garbage collector cannot release them through reference counting.

function f() {
    var o1 = {};
    var o2 = {};
    o1.a = o2; // o1 引用 o2
    o2.a = o1; // o2 引用 o1
    return;
};
f();

mark clear

The concept of mark clearing is also easy to understand. Start from the root to see if an object can be reached. If it can be reached, it will be determined that the object is still needed. If it cannot be reached, it will be released. This process is roughly divided into three steps:

a. The garbage collector creates a list of roots. Roots is usually a global variable that is referenced in the code. In js, we generally consider the global object window as the root, which is the so-called root.

b. Check all roots starting from the root, and all children will also be checked recursively, and those that can reach from the root will be marked as active.

c. The data that is not marked as active is considered no longer needed, and the garbage collector starts to release them.

When an object has zero references, we must not be able to reach it from the root; but conversely, what cannot be reached from the root is not necessarily zero references in the strict sense, such as circular references, so mark removal is better than reference counting.

Since 2012, all modern browsers use the mark-and-sweep garbage collection algorithm, with the exception of the older version IE6.

 ❀ How to avoid memory leaks

We already know the principle of garbage collection, so how can we avoid the embarrassment of creating objects that cannot be recycled and causing memory leaks? Let's talk about the four common js memory leaks.

1. Global variables

It is common sense for js developers to create as few global variables as possible, but the following two ways may accidentally create global variables. The first is to declare variables in functions without using var:

function fn() {
    a = 1;
};
fn();
window.a //1

In the above code, we declare a variable a in the function body. Since var is not declared, even in the function body, it is still a global variable. We know that a global variable is equivalent to adding properties to the window, so we can still access it after the function is executed.

The second is to create variables through this in the function body:

function fn() {
    this.a = 1;
};
fn();
window.a //1

We know that when the function fn is called directly, it is equivalent to window.fn(), so this in the function body will point to window, so essentially a global variable is created.

Of course, the above problems are not unsolvable. We can use strict mode to avoid this problem. Try adding 'use strict' to the header of the code, and you will find that a cannot be accessed, because in strict mode, the global object points to undefined.

Sometimes we cannot avoid using global variables, so remember to release them manually after use, for example, let the variables point to null.

2. Forgotten timer or callback function

var serverData = loadData();
setInterval(function () {
    var renderer = document.getElementById('renderer');
    if (renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 3000);

In the above code, when the dom element renderer is removed, the timer callback function cannot be recycled because of the periodic timer, which also causes the timer to keep referencing the data serverData. Stop the timer when not needed.

For example, when we use event listening, remember to remove the listening event if you no longer need to listen.

var element = document.getElementById('button');

function onclick(event) {
    element.innerHTML = 'text';
};

element.addEventListener('click', onclick);
// 移除监听
element.removeEventListener('click', onclick);

3. Closure

Closures are extremely common in js development, let's look at an example:

var theThing = null;
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        //unused未执行,但一直保持对theThing的引用
        if (originalThing)
            console.log("hi");
    };
    //创建一个新对象
    theThing = {
        longStr: new Array(1000000).join('*'),
        someMethod: function () {
            console.log("message");
        }
    };
};

setInterval(replaceThing, 1000);

Every time the timer calls replaceThing, theThing will get a new object containing the array longStr and the closure someMethod.

The closure unused holds a reference to the object originalThing, and because of the assignment of theThing, it also holds a reference to theThing. Although unused is not executed, the reference relationship will cause originalThing to be unable to be recycled, so theThing will also be the same. The correct way is to add originalThing = null at the end of replaceThing;

So we often say that for the variables in the closure, you must remember to release them manually when you don't need them.

4. DOM reference

It is always considered bad to operate dom, but it must be operated. Our habit is to store it through a variable, so that it can be used repeatedly, but this will also cause a problem, dom will be referenced twice.

var elements = document.getElementById('button')

function doStuff() {
    elements.innerHTML = '听风是风';
};
// 清除引用
elements = null;
document.body.removeChild(document.getElementById('button'));

In the above code, one reference is a reference based on the dom tree, and the second is a reference to the variable elements. When we do not need this dom, we do two clearing operations.

Guess you like

Origin blog.csdn.net/qq_47443027/article/details/126786557