The introduction

JavaScript is a language with Garbage Collection (GC) mechanism, which saves developers from wasting too much energy on memory management, improving development efficiency and error tolerance of code. Each engine’s GC algorithm is more or less different, but it can also leak memory inadvertently during development.

Memory life cycle

  • Allocate memory – C allocates memory through the malloc() method. JavaScript allocates memory automatically when creating variables (objects, strings, etc.).
  • Use memory – Read and write operations in the obtained memory.
  • Free memory – C frees memory through the free() method, while JavaScript’s GC mechanism tracks memory allocation and usage to find when allocated memory is no longer needed, in which case it frees it automatically.
let n = 123;           // Allocate memory for numeric variables
let s = "azerty";      // Allocate memory to the string
function f(a){
  return a + 2;
}                      // Allocate memory for functions (callable objects)
let d = new Date(a);// Assign a Date object
let e = document.createElement('div'); // Assign a DOM element
Copy the code

Two common GC algorithms

Key terms:

  • Object – “a collection of memory that supplies data used by a program.” It consists of a head and a field. A header is similar to a packet header in a computer network and is used to assist GC algorithm implementation. Fields are used to store data (I understand as values), which is a general name. A field contains multiple filed records.

  • Root – All objects in the GC domain form a tree. Root is the Root of the tree, which can be understood as the browser’s Window object or node.js’s Global object.

1. Mark clearing algorithm

  • B and f are directly referenced by the root node and can be understood as global variables.
  • B refers to C, f refers to E, E refers to F, A refers to D, but is not referenced by the root, g is not referenced, and no other object is referenced.

(1) In the marking stage, the function is to take the root node as the starting point, and use depth-first search to traverse all objects downward and mark the head of the searched object. According to the reference relationship in the figure, b and E, which are directly referenced by the root node, and C and F, which are referenced by both nodes, are added with marking information. B, D, and G are not marked.② In the clearing stage, all unmarked objects are deleted based on the marked results, and the marked information in the header of the marked objects is clear so that the next GC process can proceed smoothly.(3) Merge. After the unmarked object is cleared, the corresponding memory space becomes an idle state, causing the object to be stored in the memory in blocks. This state is called fragmentation. The hazards of fragmentation are as follows: 1. The query efficiency of partitioned memory is much lower than that of continuous memory. 2. Waste storage space. For example, three 2M chunks of memory are not sufficient for applications that require 4M memory, so free memory is often merged after cleaning is complete.

var m = 0,n = 19      // mark m,n,add() to enter the environment.
add(m, n)             // mark a, B, and c as entering the environment.
After the function completes, a, B, and C are marked as leaving the environment, waiting for garbage collection.
function add(a, b) {
  a++
  var c = a + b
  return c
}
Copy the code

2. Reference counting algorithm

The GC algorithm used by IE6 and IE7 engines is based on Microsoft’s COM technology and follows the same GC strategy. Reference counting works like this: each object has a counter in its head that keeps track of how many objects are used. Each time a new reference is made, the counter +1 and vice versa. When the counter value is 0, there is no reference and can be safely recycled and destroyed.



Reference counting has a disadvantage: it can’t handle circular references. In the figure above, A and D refer to each other, but are not referenced by any other object and will not be recycled if they are not used.

function f() {
  let o1 = {};
  let o2 = {};
  o1.p = o2; // o1 references o2
  o2.p = o1; // o2 refers to o1. A circular reference is made here
}
f();
Copy the code

A memory leak

  • JavaScriptIt has its own memory reclamation mechanism to determine which variables are no longer needed and remove them. But when code has logic flaw that has not required, but there can also be a reference in the program, cause the program to run after no suitable recycling occupied space, cause memory utilization, the more occupied the longer run, the resulting, poor performance, high latency, frequent collapse.

Common memory leaks and ways to avoid them

1. Incorrect introduction of global variables

When referencing an undeclared variable in non-strict mode, a new variable is created in the global object. In the browser, the global object will be window

function foo() { 
    bar1 ="some text"; // bar will leak globally.
    / / or
    this.bar2 = new Array(100) // arr is also referenced by window
}
foo()
Copy the code

Global variables by definition cannot be collected by the garbage collection mechanism, and there are naming conflicts, breaking encapsulation and other hazards. Special attention should be paid to global variables used for temporary storage and processing of large amounts of information. Workaround: Variables inside a function need to be declared before they are used; Strict mode (direct assignment of undefined variables raises ReferenceError); If you must use a global variable to store data, be sure to specify it as NULL or reassign it when you’re done

2. Destroy timers and callbacks in time

const someResource = getData();
setInterval(function() {
    let node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
        // The timer is not cleared either
    }
    // Node/someResource stores a large amount of data that cannot be recycled
}, 1000);
Copy the code

Reason: Timers associated with nodes or data are no longer needed, node objects can be deleted, and the entire callback function is no longer needed. The timer is not clear. It keeps running. Also, someResource can’t be recycled if it stores a lot of data. Solution: Manually clear the timer when the timer is finished

3. Be careful with closures

 function fn1() {
    var a = 4
    return function fn2() {
      console.log(++a)
    }
  }
  var f = fn1()
  f() / / 5
  f() // 6 Variable A is always in memory
Copy the code

Closures are used primarily to design private methods and variables. The advantage of closure is that it can avoid the pollution of global variables, but the disadvantage is that the closure will make the variables in the function be stored in memory, which will increase the memory usage, and improper use will easily cause memory leakage. F = null; When using closures, scope your data properly, figuring out what goes in which outer scope and what goes in the returned function.

4. Be aware of references outside the DOM and timely remove events added to the DOM

var dom = document.getElementById('domID');
document.body.removeChild(dom); // dom is deleted
console.log(dom);  // But there are references
// Can console out that the entire DOM element is not recycled
Copy the code

Reason: Variable references to the DOM exist in memory even when removed from the DOM tree. Dom = null

var button = document.getElementById('button');
function onClick(event) {
    button.innerHTML = 'text';
}
button.addEventListener('click', onClick);
Copy the code

Cause: Added an event handler onClick to the element button that uses a button reference. Older versions of IE were unable to detect cyclic references between DOM nodes and JavaScript code, leading to memory leaks. Solution: the button. The removeEventListener (‘ click ‘);

Today, garbage collection algorithms in modern browsers, including Internet Explorer and Microsoft Edge, detect and handle circular references correctly. In other words, you don’t have to call removeEventListener to reclaim node memory. (Isn’t that a bad idea?)

React memory leaks are common

There are two common situations:

1. Use setTimeout() and execute setState() in the callback function. If leaving the page before the callback is executed causes the target component to be unloaded, then an out-of-memory error will occur after the callback is executed.

Solutions:Clear timers when components are uninstalled

// class component
componentDidMount() {
    // Memory leak caused by timer setState execution
	this.timeout = setTimeout(function () {
		setState(data);
	}, 1000);
}

componentWillUnmount() {
	// Clear setTimeout() before the component is uninstalled
	clearTimeout(this.timeout);
}
Copy the code
// function component
useEffect(() = > {
	const timeout = setTimeout(function () {
		setState(data);
	}, 1000);
	
	// Return clean up. React executes this function when the component is about to be uninstalled
	return () = > {
		clearTimeout(timeout); }; } []);Copy the code

2. SetState () is executed in the callback of the asynchronous request, and the target component is unloaded before the callback completes. Solutions:

  • Method 1: define variables, do not perform setState operation after component destruction, do not cancel HTTP request.
  • Method two: Cancel the HTTP request after component destruction.

Only the method of a function component is shown here:

useEffect(() = > {
    let isUnmounted = false; // Define variables
    (async() = > {const res = await fetch(SOME_API);
        const data = await res.json();
        if(! isUnmounted) {// No setState operation is performed when the value is true
            setValue(data.value);
            setLoading(false);
        }
    })();

    return () = > {
        isUnmounted = true; }} []);Copy the code

Method 2: AbortController is an experimental interface for the browser that returns a semaphore (Singal) to abort the sent request.

useEffect(() = > {
    const abortController = new AbortController(); / / create
    (async() = > {const res = await fetch(SOME_API, {
            signal: abortController.signal, // passed in as a semaphore
        });
        const data = await res.json();
        setValue(data.value);
        setLoading(false); }) ();return () = > {
        abortController.abort(); // Interrupts during component uninstallation}} []);Copy the code
let xhr = new XMLHttpRequest();
xhr.method = 'GET';
xhr.url = 'https://slowmo.glitch.me/5000';
xhr.open(method, url, true);
xhr.send();

// Abort the request at a later stage
abortButton.addEventListener('click'.function() {
  xhr.abort();
});
Copy the code

Detection of memory leaks

For details, see Chrome Memory Leak (1) and Memory Leak Analysis Tool (1). Browser method

  • Use the developer tools of the browser. Select Timeline for the lower version and Memory for Performance for the higher version.
  • Click the record button in the gray circle in the upper left corner.
  • Various operations are carried out on the page to simulate the use of users.
  • After a certain period of time, click the Stop button and the panel will display the memory usage for that period.

As shown in the figure below, the curve is basically stable, and it is normal for the data to fluctuate within a certain range. If the curve and related data remain high, there may be a memory leak

(a)(2) 2. Server environment

Use what Node providesprocess.memoryUsageMethods. referenceES6 WeakMap

console.log(process.memoryUsage());

/ / output
{ 
  rss: 27709440.Resident set size, all memory usage including instruction area and stack
  heapTotal: 5685248.// The memory occupied by "heap", both used and unused
  heapUsed: 3449392.// The part of the heap used
  external: 8772        // memory occupied by C++ objects in V8 engine
}
// Determine memory leaks using the heapUsed field.
Copy the code

How to optimize GC

  • Design pages properly, create objects/render pages/load images on demand, avoid requesting all data at once.
  • Objects that are no longer used are manually assigned to null. Reduce the number of memory scanning operations.
  • useWeakMapWeakSet
  • Added listeners need to be removed.
  • Do not use console.log large objects such as DOM, large arrays, ImageData, and ArrayBuffer in the production environment. Because console.log objects are not garbage collected.
  • When preloading an image (using the dynamic create IMG setting SRC), assign the IMG object to null, otherwise the image memory will not be freed. When the image is actually rendered, the browser reads it again from the cache.
  • .

Refer to the link

V8 Memory management Mechanism In-depth understanding of browser memory management