preface

Why care about memory?

If there is an overflow of memory, the application will take up more and more memory, eventually causing the application to stall, or even unresponsive performance experience problems. This article from the memory data structure, memory space management, memory reclamation algorithm, memory leakage scenarios for a comprehensive introduction to the memory mechanism.

The data structure

Understanding data structures is fundamental to understanding memory space management and memory mechanisms. Let’s take a look at the features and usage scenarios of data structures in JavaScript.

There are three data structures in JavaScript: stack, heap and queue.

1. The stack

The stack is a last in, first out (LIFO) data structure

Think of the stack as a container like a cup. When we put stones in the cup, each stone is placed at the top of the cup. When the stone is taken out, the stone at the top of the cup is taken out first. This is called lifO

Note: Both the values of the base data type and the addresses referencing the data type are stored on the stack

2. The heap

A heap can be thought of as an array object with a complete binary tree with some rules

A heap has two characteristics:

  • A node in the heap is always no larger or smaller than its parent
  • The pile is always oneComplete binary tree

For example, draw the array object [10, 7, 2, 5, 1] as a binary tree (top down, left to right) :

Is a complete binary tree structure, and each child node is not larger than the parent node value, is a heap structure. The heap with the largest root is called the big root heap.

Let’s invert the above array [1, 5, 2, 7, 10] and draw it as a binary tree, which is also a complete binary tree, and each child node is no larger than the parent node, which is a heap structure. The heap with the smallest root is called a small root heap.

Note: Values referencing data types are stored in the heap.

3. The queue

A queue is a first-in, first-out (FIFO) data structure

Queue is very easy to understand, you can imagine that we queue to buy KFC, each time the new person can only queue at the back of the queue, the first person in line to order first leave, this is the first in first out

Note: Queues are the basic data structures for Event loops, which will be analyzed in detail in a separate article.

Memory space management

The memory life cycle of JavaScript can be divided into three stages: allocating memory, using memory, and releasing memory.

1. Allocate memory

When we declare variables, functions, and objects, the system automatically allocates memory for them.

We already know that JavaScript has two types of memory: stack memory and heap memory. How does JavaScript allocate memory?

In the variable storage type, we introduced in detail that there are two types of variables: basic data type and reference data type. The system will allocate different types of memory (stack memory or heap memory) according to different data amount types.

  • 1)Basic data typesIs fixed in size and is allocated by the system at compile timeStack memory
let age = 10; // Allocate to stack memory
let name = Valley Floor Dragon; // Allocate to stack memory
Copy the code
  • 2)Reference data typeValues are complex (for example, objects can contain multiple primitive types, etc.). The size of values is not fixed. Therefore, values are stored inHeap memoryIn the,Stack memoryIs stored in its reference address.
// Allocate the reference address of the object INFO to stack memory, and allocate the object INFO and its contents to heap memory
let info = {
  name: Valley Floor Dragon.age: 10
}

// Allocate the reference address of function f() to stack memory, store the contents of f() as a string in the heap memory
function f(){
   console.log(I'm a function.)}Copy the code

2. Use memory

When we read and write variables and objects, assign values, and call functions, we are using memory

Basic data types: Access directly from stack memory, faster

let age = 10; // Allocate to stack memory
let name = Valley Floor Dragon; // Allocate to stack memory

// Access variables using memory
console.log(`my name is ${name}, my age is ${age}`)
Copy the code

Reference data type: When accessing, the reference address is first accessed from the stack memory, and then the corresponding value is retrieved from the heap memory through the address, which is slower

// Allocate the reference address of the object INFO to stack memory, and allocate the object INFO and its contents to heap memory
let info = {
  name: Valley Floor Dragon.age: 10
}
// Access objects, using memory
console.log(info);

// Allocate the reference address of function f() to stack memory, store the contents of f() as a string in the heap memory
function f(){
   console.log(I'm a function.)}// Call function, use memory
f();
Copy the code

3. Release memory

JavaScript has an automatic garbage collection mechanism, where the garbage collector performs a free operation every once in a while to find values that are no longer in use, and then frees their memory

Local variables: Local variables defined inside a function that are released automatically after the function completes execution

function f() {
  // Define the variable name and the object info inside the function
  var name = Valley Floor Dragon
  var info = { name }
  
  /* The memory occupied by the name and info function is automatically released */
}
// Execute the function
f();
Copy the code

Global variables: It can be difficult to determine when global variables need to automatically free memory, so avoid using them in your development.

Memory reclamation algorithm

The memory reclamation algorithm used by earlier browsers was a reference counting algorithm. Since 2012, all browsers have changed to a token clearing algorithm

1. Reference counting algorithm (old algorithm)

When a value is referenced once (assignment, held by another object, etc.), the counter is +1. If the reference is released, the counter is -1. When the number of references is 0, the value is no longer in use and can be released

Reference counting algorithms have one drawback: circular references

var obj1 = {name: Valley Floor Dragon};
var obj2 = {age: 28};

obj1.a = obj2;  // obj1 holds obj2
obj2.b = obj1; Obj2 holds obj1

// Verify that a circular reference exists
console.log(JSON.stringify(obj1));
Copy the code

Obj1 and obj2 hold each other, and there is the problem of circular references. With a reference counting algorithm, the counter can never be zero, that is, the memory of objects with circular references is not reclaimed.

Note: You can use json.stringify () to determine if an object has a circular reference, and if it does, an error will be reported

2. Mark Clearing algorithm (new algorithm)

The mark clearing algorithm can also be understood as the clearing algorithm based on the execution environment. When entering the execution environment, it is marked as entering the environment, and when leaving the execution environment, it is marked as leaving the environment.

Each execution environment is associated with a variable object that saves all variables and functions in the environment. When leaving the execution environment, that is, when all the code in the environment is executed, the variable object will be destroyed, and the memory occupied by variables and functions in the execution environment will be reclaimed

Global execution environment: The global execution environment in JavaScript is the outermost layer of environment. In the browser, the global environment is Window. Global variables and global functions are hung under the window, and the global execution environment is destroyed only when the program exits or closes the browser.

Local execution environment: Generally refers to the function execution environment. Each function will generate a local execution environment when it is called. When the function is finished, the function execution environment will be destroyed.

function f(){
   var name = Valley Floor Dragon; // mark "enter environment"
   console.log(name);
}
f(); // When the function completes, name is marked "out of the environment" and memory is reclaimed
Copy the code

If you don’t have a clear understanding of the execution environment, I suggest you read my article to understand the execution context and execution stack

The problem of circular references can be solved by using the tag clearing algorithm for memory reclamation.

Memory Leak scenario

In simple terms, a memory leak is a variable that should have been destroyed, but was not reclaimed because of bad code

1. Unexpected global variables

  • A local variable inside a functionundefined, causing it to accidentally become a global variable
"use strict"
function f() {
   // Name is undefined. By default, it is a global variable and hangs on the browser's global object Window
   name = Valley Floor Dragon;
}
// Will print "valley bottom Dragon", indicating that the name is not defined, is changed to a global variable
console.log(window.name);
Copy the code

In this case, the name is undefined and is actually hung on the global object (the browser’s global object is window), which cannot be destroyed along with the execution environment of the function, resulting in a memory leak.

  • thisGlobal variable that causes an accident
function f() {
   // This refers to the global object Window
   this.name = Valley Floor Dragon;
}
f();
// It will print "Valley Floor Dragon"
console.log(window.name);
Copy the code

F () is equivalent to window.f(), so this inside the function refers to the global object window, and the variable name becomes the global variable.

Solution: Use strict in the header of the code file. In this case, this in the function refers to undefined, and an error message is given when executing

"use strict"
function f() {
   // This refers to undefined
   this.name = Valley Floor Dragon;
}
f();
Copy the code

2. The forgotten timer or observer

  • When I do not need tosetIntervalorsetTimeoutWhen the timer was notclearTimer callbacks and internal dependent variables cannot be collected, resulting in memory leaks.
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Process node and someResource
        node.innerHTML = JSON.stringify(someResource)); }},1000);
Copy the code

When the timer is no longer needed, the Node object can be deleted and the entire callback function is no longer needed. However, the timer callback function is still not recycled (it is not recycled until the timer stops). Also, someResource can’t be recycled if it stores a lot of data.

Solution: Call clearTimeout() or clearInterval() to stop the timer when it is no longer needed (the page will be destroyed, for example).

  • Event listeners are registered when they are not needed (such as page destruction), without removing observers, and are used in older browsersReference counting algorithmReclaiming memory) causes memory leaks.
var element = document.getElementById('button');
function onClick(event) {
    element.innerHTML = 'text';
}
element.addEventListener('click', onClick);

Copy the code

For observer and circular references, circular references are properly detected and handled in newer versions of browsers (which reclaim memory using a tag clearing algorithm), and removeEventListener is no longer necessary to reclaim node memory.

3. The reference of the DOM

  • DOMThe life cycle of an element depends on whether it hangsThe DOM treeWhen removed from the DOM tree, it can be destroyed for recycling.
  • But if theDOMIt’s also referenced by other JS objects, soDOMThe life cycle of an element depends onThe DOM treeAnd the js object that holds it, need to release the associated references, otherwise, there will be a memory leak
var elements = {
    button: document.getElementById('button')};function removeButton() {
    // The button is a descendant of the body
    document.body.removeChild(document.getElementById('button'));
    // At this point, there is still a global #button reference
    // elements dictionary. The button element is still in memory and cannot be collected by GC.
}
Copy the code

If you store the DOM as a dictionary (JSON key-value pairs) or an array, the same DOM element has two references: one in the DOM tree and one in the dictionary. Both references will need to be cleared in the future.

4. The closure

The key to closures is that anonymous functions can access variables in the parent scope. Closures can hold local variables in a function without freeing them.

// the function is nested inside the function to form a closure
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function () {
     // Even if it is a empty function}}Copy the code

When the event callback is defined in the above example, it forms a closure because the function is defined within the function and the inner function — the event callback — refers to the outer function.

Solutions:

  • Define event handlers outside
// Define the event handler outside
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = onclickHandler
}
Copy the code
  • Or remove references to the DOM in external functions that define event handlers
// Remove the reference to the DOM in the external function that defines the event handler
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function() {
    // Even if it is a empty function
  }
  obj = null
}
Copy the code

reference

  • Data structure: Heap
  • How do I determine if a circular reference exists in a JS object