Fix javascript memory leaks

Author: Guo Zhenghong 2021/5/6

Preface:

Free to learn, this is a pursuit of technical people should be the way of life, just today I am very free, so to learn about memory leakage knowledge, start ❤️

What is a memory leak

Before we understand what a Memory leak is, we should have an idea of what Memory is. Random Access Memory (RAM) is internal Memory that exchanges data directly with the CPU. It can be read and written at any time and is fast, often serving as a temporary data storage medium for operating systems or other running programs.

What is a memory leak? :

A memory leak is memory that a program no longer needs, but is not released in time!

In some low-level languages, such as C, memory needs to be allocated and released by the developers themselves, through malloc, free and other functions for memory management.

char * buffer;

// Use malloc to allocate memory space

buffer = (char*) malloc (66);

// do something ...

// Free the memory space reference when no longer needed

free(buffer);

Copy the code

In Chrome, there is a V8 engine that automatically allocates and reclaims memory. This is the garbage collection mechanism. But that doesn’t mean we don’t have to worry about memory when writing code, because V8 garbage collection has certain rules, and knowing those rules can help us avoid writing bad code that leaks memory.

Let’s take a look at two common methods of javascript garbage collection:

  1. Reference counting algorithm
  2. Mark clearing algorithm

Reference counting method

Internet Explorer uses the reference counting algorithm, which cannot solve the garbage collection problem of circular references and is prone to memory leakage

So what is a reference counting algorithm? What is the circular reference problem?

Reference counting means that we have a variable and every time it is referenced the GC mechanism increments the variable count by one, decreases the count by one when the references decrease, and if the count is zero, it will be released in the next garbage collection.

let obj = {}; // obj counts to 0

let a = obj; // obj counts to 1

let b = obj; // obj count is 2

let a = null// obj counts to 1

let b = null// The Obj count is 0, and the next garbage collection will free the Obj memory

Copy the code

The above code demonstrates the reference counting garbage collection mechanism, which can’t be saved when there are circular references

let obj = {};

let obj2 = {};

obj.o = obj2; // obj2 counts to 1

obj2.o = obj; // obj counts to 1

Copy the code

This is a circular reference, so the garbage collection mechanism does not free the memory of obj, obj2, and the variable is resident in memory, causing a memory leak.

So with reference counting out of the way, let’s take a look at the current garbage collection algorithm used by major browsers, tag scavenging,

As of 2012, all modern browsers use the tag sweep garbage collection algorithm. All of the improvements to JavaScript garbage collection algorithms are based on improvements to the mark-sweep algorithm, not on improvements to the mark-sweep algorithm itself and its simplified definition of whether an object is no longer needed.

Mark clearance

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 objects referenced from the root, and continue to find objects referenced by those objects.

Before we start talking about tag cleaning, one more thing is the concept of stacks and heaps. Look at the following example

let obj = {};

let obj2 = {};



obj = null;

obj2 = null;

Copy the code

As we know, in javascript, all but the eight basic types (eight so far) are object types. In JS, object types are reference types, and entities of content are stored in the heap, as I’ve drawn below:

image-20210506132014962

When we reassign obj obj1, the memory structure will look like this

image-20210506132150699

Of objects in the heap memory did not refer to them, but they also occupy the memory, this needs us to destroy them playing the garbage collection, garbage collection mechanism of V8 engine not only destroy if no one quoted in the heap memory space, and also to defragment heap memory, the V8 GC (GC) work as shown in the diagram below:

The picture

V8 GC can be broken down into the following steps

The first step is to mark active and inactive objects in the space by GC Root. Currently, V8 adopts the accessibility algorithm, which starts from GC Root to traverse all objects. The marks that can be traversed by GC Root are accessible and called active objects and must be kept in memory. The marks that cannot be traversed by GC Root are unreachable and called inactive objects. These unreachable objects will be cleaned up by the GC.

The second step is to reclaim memory occupied by inactive objects. All objects marked as recyclable in memory are cleaned uniformly after all tags are completed.

Step three, do memory defragment. Generally speaking, after frequent object collection, there will be a large number of discontinuous memory space, we call these discontinuous memory space memory fragmentation.

The V8 engine uses two garbage collectors, the Major collector and the Minor collector (Scavenger). You may ask what the generation hypothesis is:

The first is that most objects are “dead”, which means that most objects live in memory for a short time, such as variables declared inside a function or in a block-level scope, which are destroyed when the function or code block finishes executing. So once memory is allocated, objects of this class quickly become inaccessible;

The second is undead objects that live much longer, such as the global Window, DOM, Web API, and so on.

These two recyclers do the following:

  • Main garbage collector -Major GC, which is responsible for old generation garbage collection.
  • Garbage collector -Minor GC (Scavenger) is responsible for garbage collection of the new generation.

This brings up the concept of new generation memory and old generation memory, dividing the heap into two areas

The memory area of the new generation is generally small, but garbage collection is more frequent, while the characteristics of the memory area of the old generation are that objects occupy relatively large space, objects live for a long time, and garbage collection frequency is low.

Incidentally, garbage collection blocks the process.

2. Common memory leaks

Now that you know what garbage collection and memory leaks are, let’s look at some common memory leak scenarios:

  1. Unexpected global variables

As we mentioned earlier, some objects are resident in memory, considered immortal objects, such as the Window object, which is javascript’s top-level object in the browser. It exists throughout the javascript life cycle, and if we accidentally attach a large, unusable variable to the Window object, There will be a memory leak, which is of course a very low-level error.

function test({

  // Missing the declaration, will be automatically mounted to the window object

  str = ' ';

  for (let i = 0; i < 100000; i++) {

   str += 'xx';

  }

  return str;

}

// After the test execution, STR should be useless, but it is resident in memory

test();

Copy the code
  1. Abuse of closure

Here, by the way, I want to understand the concept of closure. The concept of closure is quite abstract on the Internet. My personal understanding of closure is:

The lexical environment of a function and other scoped variables on which it operates is called a closure

Of course, if I were a bar geek, I might say does the with syntax count as a closure? By definition, with is not a function and therefore not a closure.

Closures are described in MDN:

A combination of a function bound to (or surrounded by) references to its surrounding state (lexical environment) is a closure. That is, closures allow you to access the scope of an outer function within an inner function. In JavaScript, whenever a function is created, the closure is created at the same time the function is created.

Closures are a feature unique to statically scoped (aka lexical scoped) languages.

function fn({

  const x = 'xx';

  return function({

    return x;

 }

}

const getX = fn();

console.log(getX());

Copy the code

The example above is a closure. The internal variable is not destroyed after fn execution. We can access the variable x in fn function scope by getX in global scope.

When you look at static scope, you might ask, is there any dynamic scope? But there is. Bash scripts use dynamic scope, javascript uses static scope, and closures are the functionality of static scope.

Let’s look at two examples

const x = 123;

function fn({

  console.log(x);

}

function fn2({

  const x = 345;

  fn();

}

fn2(); // It is 123

Copy the code

Static scope: the scope of x output in FN is determined at definition time

Let’s take another example of dynamic scoping

# test.sh

value="global";

function fn() {

 echo $value;

}

function fn2() {

 local value="local";

 fn;

}

fn2; # local

Copy the code

Dynamic scope: the scope of the x output in FN is determined at call time

One more point is that a memory leak can only be called if a closure is abused, because by definition a memory leak is a memory leak that is no longer used and has not been destroyed. The values in a closure are the ones we use and therefore should not be called a memory leak.

function generateRandomMath({

 let x = Math.random();

  return function({

    return x;

  }

}

Copy the code

If the above example is an abusive closure, it should be called a memory leak

  1. The forgotten timer

There’s nothing to say about this, just set a timer and remember to use clearInterval or clearTImeout to turn it off when you don’t want to.

  1. DOM related

A number of events are bound to a DOM node, and the DOM node is removed but memory is freed during use

Let’s look at an example

<button class="remove">remove bbb</button>

<div class="box">bbb</div>

<script>

  const box = document.querySelector('.box');



  document.querySelector('.remove').addEventListener('click', () = > {

    document.body.removeChild(box);

    console.log(box);

  })

</script>

Copy the code

After removing the box element, the box still occupies memory, which is also a memory leak because the box is no longer used but does not free memory

3, summarize

The above is the summary of my learning process, write it down for future review, by the way, I hope to share a little help to see this article you want to exchange learning can follow my wechat public number GZHDEV bye-bye ❤️