MEMEPh. ideas that are worth sharing...

Garbage Collection and Memory Leaks in JavaScript

Foreword


A program needs memory to run. The operating system or runtime must supply memory whenever a program asks for it. The so-called memory leak is simply the memory that is no longer used and has not been released in time. In order to better avoid memory leaks, we first introduce the Javascript garbage collection mechanism.

In languages ​​such as C and C++, developers can directly control the application and recovery of memory. But in Java, C#, and JavaScript languages, the application and release of variable memory space are handled by the program itself, and developers do not need to care. That is to say, Javascript has an automatic garbage collection mechanism (Garbage Collecation).

 

The need for garbage collection


The following passage is quoted from The Definitive Guide to JavaScript (4th Edition)

Since strings, objects and arrays do not have a fixed size, they can only be dynamically allocated when their size is known. Every time a JavaScript program creates a string, array, or object, the interpreter must allocate memory to store that entity. As long as memory is dynamically allocated like this, it must eventually be freed so that it can be reused, otherwise, the JavaScript interpreter will consume all available memory in the system, causing the system to crash.

This passage explains why the system needs garbage collection. Unlike C/C++, JavaScript has its own garbage collection mechanism.

The mechanism of JavaScript garbage collection is very simple: find variables that are no longer used, and then release the memory occupied by them, but this process is not constant, because its overhead is relatively large, so the garbage collector will periodically follow a fixed time interval. implement.

var a = "boating in the waves";
var b = "Frontend Artisan";
var a = b; //override a

After this code runs, the string "boating in the waves" loses its reference (it was previously referenced by a). After the system detects this fact, it releases the storage space of the string so that it can be reused.

 

2. Garbage collection mechanism


How does the garbage collection mechanism know which memory is no longer needed?

There are two methods of garbage collection: mark removal and reference counting . Reference counting is less common, and mark-sweeping is more common.

 

1. Mark Clear

This is the most common way of garbage collection in javascript . When a variable enters the execution environment, mark the variable as "entering the environment". Logically, the memory occupied by variables entering the environment can never be freed, because they may be used as soon as the execution flow enters the corresponding environment. When a variable leaves the environment, it is marked as "leaving the environment".

When the garbage collector runs, it marks all variables stored in memory. Then, it strips the variables in the environment and tags referenced by variables in the environment. Variables marked after this will be treated as variables ready for deletion, because variables in the environment are no longer accessible. at last. The garbage collector completes the memory cleanup, destroys those marked values, and reclaims the memory space they occupy.

Let's use an example to explain this method:

var m = 0,n = 19 // Mark m,n,add() as entering the environment.
add(m, n) // Mark a, b, c as entering the environment.
console.log(n) // a,b,c are marked as leaving the environment, waiting for garbage collection.
function add(a, b) {
a++
var c = a + b
return c
}

 

2. Reference counting

The so-called "reference count" means that the language engine has a "reference table", which saves the reference times of all resources (usually various values) in the memory. If the number of references to a value is 0, it means that the value is no longer used, so the memory can be released.

In the above figure, the two values ​​in the lower left corner do not have any references, so they can be released.

If a value is no longer needed and the reference count is not 0, the garbage collection mechanism cannot release the memory, resulting in a memory leak.

var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
console.log('boating in the waves');

In the above code, the array [1, 2, 3, 4] is a value that occupies memory. The variable arr is the only reference to this value, so the reference count is 1. Although arr is not used in the code that follows, it continues to occupy memory. As for how to release memory, we will introduce it below.

 

In the third line of code, the variable arr referenced by the array [1, 2, 3, 4] has obtained another value, then the reference times of the array [1, 2, 3, 4] is reduced by 1, and at this time, the reference times If it becomes 0, it means that there is no way to access this value, so the memory space occupied by it can be recovered.

But reference counting has the biggest problem: circular references

function func() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 references obj2
obj2.a = obj1; // obj2 references obj1
}

When the function func is executed, the return value is undefined, so the entire function and internal variables should be recycled, but according to the reference counting method, the reference times of obj1 and obj2 are not 0, so they will not be recycled.

To solve the problem of circular references, it is best to manually set them to null when not in use. The above example can do this:

obj1 = null;
obj2 = null;

 

3. What situations can cause memory leaks?


Although JavaScript will automatically garbage collect, but if our code is written improperly, the variable will always be in the state of "entering the environment" and cannot be recycled. Here are some common cases of memory leaks:

 

1. Unexpected global variable

function foo(arg) {
   bar = "this is a hidden global variable";
}

bar is not declared, it will become a global variable and will not be released until the page is closed.

Another unexpected global variable may be thiscreated :

function foo() {
this.variable = "potential accidental global";
}

// foo calls itself, this points to the global object (window)
foo();

Add 'use strict' to the header of your JavaScript file to avoid such errors. Enable strict mode parsing of JavaScript to avoid unexpected global variables.

 

2. Forgotten timer or callback function

var someResource = getData();
setInterval(function() {
  var node = document.getElementById('Node');
  if(node) {
    // handle node and someResource
  node.innerHTML = JSON.stringify(someResource));
} }
}, 1000);

Such code is very common. If the element with id of Node is removed from the DOM, the timer will still exist. At the same time, because the callback function contains a reference to someResource, the someResource outside the timer will not be released.

 

3. Closures

function bindEvent(){
  var obj=document.createElement('xxx')
  obj.onclick=function(){
    // Even if it is a empty function
  }
}

Closures can maintain local variables within a function so that they cannot be released. When the event callback is defined in the above example, a closure is formed because the function is defined within the function, and the internal function-event callback refers to the external function.

// define the event handler outside
function bindEvent() {
var obj = document.createElement('xxx')
obj.onclick = onclickHandler
}

// or in the external function that defines the event handler, remove the reference to the dom
function bindEvent() {
var obj = document.createElement('xxx')
obj.onclick = function() {

// Even if it is an empty function
} }
obj = null
}

The solution is to define the event handler outside, lift the closure, or delete the reference to the dom in the outer function that defines the event handler.

 

4. DOM element references that are not cleaned up

Sometimes it is useful to save the internal data structure of a DOM node. If you want to quickly update several rows of a table, it makes sense to store each row in the DOM as a dictionary (JSON key-value pair) or an array. At this point, there are two references to the same DOM element: one in the DOM tree and the other in the dictionary. When you decide to delete these rows in the future, you need to clear both references.

var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};

function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
}

function removeButton() {
document.body.removeChild(document.getElementById('button'));
// At this point, there is still a reference to a global #button
// elements dictionary. The button element is still in memory and cannot be reclaimed by the GC.
}

Although we removed the button with removeChild, it still holds a reference to #button in the elements object, in other words, the DOM element is still in memory.

 

Fourth, the identification method of memory leak


The new version of chrome is viewed in performance:

step:

In the corresponding part of Heap in the figure, you can see that there is a periodic fall in the memory, and you can also see the cycle of garbage collection. If the lowest value after garbage collection (we call it min) and min are rising continuously, then there must be something serious. memory leak problem.

 

Some ways to avoid memory leaks:

All in all, one principle needs to be followed: things that are not needed should be returned in time

 

V. Optimization of the usage scenarios of garbage collection


1. Array array optimization

Assigning [] to an array object is a shortcut to empty the array (for example: arr = [];), but it should be noted that this method creates a new empty object and turns the original array object into A small piece of memory garbage! In fact, assigning the length of the array to 0 (arr.length = 0) can also achieve the purpose of emptying the array, and at the same time, it can realize the reuse of the array and reduce the generation of memory garbage.

const arr = [1, 2, 3, 4];
console.log('boating in the waves');
arr.length = 0 // The number can be cleared directly, and the array type remains unchanged.
// arr = []; Although the a variable is made an empty array, an empty array object is reapplied on the heap.

 

2. Reuse objects as much as possible

Objects should be reused as much as possible, especially when new objects are created in places such as loops, and they can be reused if they can be reused. Unused objects should be set to null as much as possible to be garbage collected as soon as possible.

var t = {} // A new object is created each time through the loop.
for (var i = 0; i < 10; i++) {
// var t = {};// A new object is created each time through the loop.
t.age = 19
t.name = '123'
t.index = i
console.log(t)
}
t = null //If the object is no longer in use, set it to null immediately; wait for garbage collection.

 

3. The function expression in the loop can be reused and it is best to put it outside the loop.

// It's also best not to use function expressions in loops.
for (var k = 0; k < 10; k++) {
var t = function(a) {

// Created 10 function objects.
console.log(a)
} }

t(k)

}

// Recommended usage
function t(a) {
console.log(a)
}

for (var k = 0; k < 10; k++) {
t(k)
}

t = null