JavaScript memory management study Notes

This article aims to return to the following questions:

  1. JavaScript’s popular memory management mechanism
  2. Memory leaks and closures
  3. What are some coding habits that can cause memory leaks and how can they be avoided

4. How do I use DevTool of Chrome to check memory leaks when scripts are running

JavaScript memory management mechanism

For computers, memory has a life cycle, divided into: allocation period, use period, release period; Programming languages utilize memory based on this lifecycle. In the case of JavaScript, memory allocation is automatically implemented by the language mechanism (engine). When we define variables, JavaScript allocates the memory we need. Such as:

const val1 = 'string';
let arr = [1.2.3];
var obj = {};
const func = function(arr){console.log(arr)};
Copy the code

The above code goes straight into use because memory is automatically allocated at definition time.

So when is the memory freed? When we don’t need it, it will be released, so how do we know we don’t need it? It is very difficult. Fortunately, this is not something we need to do manually, which is what JavaScript engines do.

Given these two realities, it’s easy for the front end to think that memory management is not necessary because we rarely need to manually allocate or release memory, we only have usage scenarios. But why do pages sometimes stagnate and crash? This is because of the limitations of the language mechanism to automatically free memory, so memory management needs to be done in order for programs to run well.

So let’s look at the Garbage Collection (GC) mechanism of the JS engine (V8).

Reference counting garbage collection

The simplest garbage collection mechanism is approximately understood as: when a piece of memory is referenced, the number of references is +1. As the program executes, the memory is not referenced, and the number of references is 0, then the JS engine reallocations this piece of memory. that’s all.

However, there is a drawback to this mechanism: it does not solve circular reference problems such as

function temp() {
  constva = {... };constvb = {... }; va.b = vb; vb.a = va; } temp()Copy the code

After the temp function is executed, the memory should be released, but the VA and VB reference each other once, so that the memory cannot be reclaimed.

Flag cleanup garbage collection

This algorithm simplifies the definition of “whether an object is no longer needed” to “whether an object is available”.

This algorithm assumes setting up an object called root (in Javascript, root is the global object). The garbage collector will periodically start at the root, find all the objects referenced from the root, and then find the objects referenced by those objects… Starting at the root, the garbage collector finds all reachable objects and collects all unreachable objects.

This algorithm is better than the previous one because “objects with zero references” are always unreachable, but the opposite is not necessarily so, see “circular references”.

The limitation is that objects that cannot be queried from the root object are cleared – MDN

Memory leaks and closures

A memory leak is a variable that you “don’t use” (that you can’t access), that still occupies memory space and can’t be reused. The variables inside the closure are the ones we need, not a memory leak.

A shorter mathematical statement: Closures are a subset of memory leaks (JavaScript Language Essentials pops into mind)

A simple understanding of closures

Closures are cross-scoped access variables. Closures are everywhere. The details are in the reference material of the article.

Memory leaks and memory overflows

A memory leak

  • If memory that is no longer needed is not released in time, it is called a memory leak. Memory leak refers to the behavior that some variables are not released in time during the execution of the program, and this behavior is called memory leak.
  • As the average user, the memory leak doesn’t even feel like it exists.
  • The real danger is the accumulation of memory leaks, which eventually consume all of the system’s memory. From this perspective, a one-time memory leak is harmless because it doesn’t pile up. That is, memory leaks, if they keep piling up, will eventually lead to an overflow problem

Out of memory

  • Memory overflow generally refers to that when executing a program, the program will apply for a certain size of memory from the system. When the actual memory of the system is less than the required memory, the memory overflow will be caused
  • An overflow can result in previously saved data being overwritten or later data running out of space

In JavaScript, pages tend to stutter, crash, etc. (especially browser)

Scenarios and Fixes

Although the memory reclamation mechanism of JavaScript can recycle most of the garbage memory, there are still some cases that cannot be reclaimed. A programmer can leak the browser’s memory, and the browser can’t.

Some of the following examples are in the execution environment, without leaving the current execution environment, and without triggering the mark purge.

Unexpected global variables

// In the browser console
function log() {
  a = 1 + 1;
  console.log(a);
}
// A is not declared with the declaration keyword, so it is stored in the global object. This is an accident, but it is really rare
// Too many global attributes are configured
Copy the code

Forgotten timer references

// bad
setTimeout(() = > {
  if(domLoaded) {
    findNode(){}; }},300}
// good
const timer = setTimeout(() = > {
  if(domLoaded) {
    findNode(){};
    clearTimeout(timer); }},300}
// If in Vue, unregister the timer reference before component destruction, or use the $once('hook:beforeDestroy',function(){}) hook
Copy the code

Forgotten event listener references and out-of-DOM references

const node = document.getElementById(nodeId);
node.addEventListener('click', clickFunc,false) {};
// So far so good, but if you don't remove the event listener, there are two problems:
// The first problem is that the DOM node is always occupying memory, but there is no guarantee that the DOM node is still on the page.
// That is, the validity of the DOM node cannot be guaranteed
// The second problem is that event listeners still occupy memory (dom node event listener list has been increased)

// However, if we eliminate in time (after leaving the page and finishing the operation), everything will be perfect
node.removeEventListener('click', clickFunc,false) {};
Copy the code

Too redundant closures are written

// Although this is possible, it is actually a strange way to write it
var theThing = null;
var replaceThing = function () {  
    var originalThing = theThing;  
    var unused = function () {
       if (originalThing)  console.log("hi");
    };
    theThing = {
        longStr: new Array(1000000).join(The '*'), 
        someMethod: function () {
           console.log(originalThing); }}; };setInterval(replaceThing, 1000);
Copy the code

Each time replaceThing is called, theThing creates a large array and a new object for a new closure (someMethod). Unused is a closure that references originalThing(theThing). Once a closure’s scope is created, it has the same parent scope and the scope is shared.

That is, someMethod can be used by theThing. SomeMethod shares closure scope with unused, although unused is never used, and its reference to originalThing forces it to remain in memory (to prevent it from being reclaimed).

As a result, when this code is run repeatedly, you will see the memory footprint go up, and the garbage collector (GC) will not be able to reduce it.

The Map and Set

These two new ES6 objects are extremely useful, but there are a few memory management issues (nitpicking is true, but real)

The following are memory leaks (key values are of reference type, i.e., objects)

let map = new Map(a);let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
key = null;
Copy the code

Manually clean or use weakMap

let map = new Map(a);let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
// Manually clean
key = null;
map.delete(key);

/ / use weakMap
let wmap = new weakMap();
let wkey = new Array(5 * 1024 * 1024);
wmap.set(wkey,10);
wkey = null; // that's all!
Copy the code

Similarly, Set and weakSet are the same, and will not be described again.

Why is this so? Because Map and Set, in order to be enumerable, use key strength reference, meaning that even if the variable as the key is empty, the key still retains the original memory address, that is, a copy of the memory address, while weakMap and weakSet store the address of the variable. It’s called a weak reference to a key, so that when the key is empty, its key is empty, there’s no memory footprint.

RENDERENCES

  1. JS closures with memory leaks – simple book
  2. Set with keys – MDN
  3. Memory management – MDN
  4. Take a closer look at JavaScript memory leaks – segmentFault
  5. JavaScript Deep closure – Yuba
  6. Now let’s talk about closures