1. Introduction

Javascript in browsers has an automatic GC:Garbage Collecation, which means that the execution environment is responsible for managing the memory used during code execution. The idea is that the garbage collector periodically (periodically) finds variables that are no longer in use and then frees their memory. However, this process is not real-time because it is expensive and stops responding to other operations during GC, so the garbage collector periodically executes it at regular intervals.

Variables that are no longer used are variables that end their life cycle. Of course, they can only be local variables. Global variables do not end their life cycle until the browser unloads the page. Local variables exist only during the execution of a function, which allocates space on the stack or heap for local variables to store their values, and then uses them in the function until the function ends, whereas the external function does not end because of the inner function.

Or on the code description:

function fn1() {
    var obj = {name: 'hanzichi'.age: 10};
}
function fn2() {
    var obj = {name:'hanzichi'.age: 10};
    return obj;
}

var a = fn1();
var b = fn2();
Copy the code

Let’s look at how the code is executed. {name: ‘hanzichi’, age: {name: ‘hanzichi’, age: ‘hanzichi’, age: ‘hanzichi’; 10}, and when the call ends, the fN1 environment, then the block memory will be automatically released by the GARBAGE collector in the JS engine; When fn2 is called, the returned object is referred to by the global variable B, so the block is not freed.

Here comes the question: Which variable is useless? So the garbage collector must keep track of which variables are useless, and mark those that are no longer useful, so they can be reclaimed in the future. The strategy used to tag useless variables may vary by implementation, and there are usually two implementations: tag clearing and reference counting. Reference counting is less common, and tag clearing is more common.

2. Mark clearing

The most common garbage collection method in JS is tag cleanup. When a variable enters the environment, for example, declaring a variable in a function, mark the variable as “entering the environment.” Logically, you can never free up memory occupied by variables that enter the environment, because they may be used whenever the execution stream enters the corresponding environment. When a variable leaves the environment, it is marked as “out of the environment.”

function test(){
var a = 10 ;       // Get tagged and enter the environment
var b = 20 ;       // Get tagged and enter the environment
}
test();            // A and B are marked out of the environment and recycled.
Copy the code

At run time, the garbage collector marks all variables stored in memory (of course, it can be marked in any way). It then removes the tags (closures) of variables in the environment and those referenced by variables in the environment. Variables tagged after this point are considered to be ready for deletion because they are no longer accessible to variables in the environment. Finally, the garbage collector completes the memory cleanup, destroying the tagged values and reclaiming the memory space they occupy. So far, JS implementations of IE9+, Firefox, Opera, Chrome, and Safari have all used a marked sweep garbage collection strategy or similar strategies, with different garbage collection intervals.

3. Reference count

The meaning of reference counting is to keep track of how many times each value is referenced. When a variable is declared and a reference type value is assigned to the variable, the number of references to the value is 1. If the same value is assigned to another variable, the number of references to the value is increased by one. Conversely, if a variable containing a reference to that value obtains another value, the number of references to that value is reduced by one. When the number of references to this value goes to zero, there is no way to access the value, and the memory space it occupies can be reclaimed. This way, the next time the garbage collector runs, it frees up memory for values that are referenced zero times.

function test() {
    var a = {};    // the number of times a refers to an object is 1
    var b = a;     // The number of references a points to an object plus 1, which equals 2
    var c = a;     // The number of references a points to an object is increased by 1 to get 3
    var b = {};    // The number of references a points to an object is reduced by 1 to 2
}
Copy the code

Netscape Navigator3 was the first browser to use a reference counting strategy, but it soon ran into a serious problem: circular references. A circular reference is when object A contains A pointer to object B, and object B contains A reference to object A.

function fn() {
    var a = {};
    var b = {};
    a.pro = b;
    b.pro = a;
}
fn();
Copy the code

Code above cited a and b are 2, fn after the completion of execution, the two objects have to leave the environment, under the tag removal way is no problem, but under the reference counting strategy, because of a and b citations is not 0, so will not reclaimed by the garbage collector memory, if fn the function is called a lot, It will cause a memory leak. On IE7 and IE8, the memory went straight up.

As we know, there are some objects in IE that are not native JS objects. For example, objects in the memory leak DOM and BOM are implemented as COM objects using C++, and the garbage collection mechanism of COM objects uses reference counting strategy. Therefore, the COM objects accessed by JS are still based on the reference counting strategy, even though the JS engine of IE adopts the tag clearing strategy. In other words, whenever COM objects are involved in IE, there is a problem with circular references.

var element = document.getElementById("some_element");
var myObject = new Object(a); myObject.e = element; element.o = myObject;Copy the code

This example creates a circular reference between a DOM element element and a native JS object, myObject. Where the variable myObject has an attribute e pointing to the Element object; The element variable also has an attribute o that refers back to myObject. Because of this circular reference, even if the DOM in the example is removed from the page, it will never be recycled.

Here’s an example:

  • Yellow means directly referenced by a JS variable, in memory
  • Red refers to the js variable referenced indirectly, as shown in the figure above. RefB is referenced indirectly by refA, so that even if the refB variable is empty, it will not be recycled
  • The child element refB due toparentNodeAs long as it is not deleted, all of its parent elements (shown in red) are not deleted

Another example:

window.onload=function outerFunction(){
    var obj = document.getElementById("element");
    obj.onclick=function innerFunction(){};
};
Copy the code

This code looks fine, but obj references document.getelementById (‘ Element ‘), and the onclick method of document.getelementById (‘ Element ‘) references variables in the external environment, Including OBJ, of course. Isn’t that hidden? (Newer browsers remove Node events when they are removed, but older browsers, especially Internet Explorer, have this bug)

Solutions:

The easiest way to do this is to manually unreference the loop yourself, as the previous function did

myObject.element = null;
element.o = null;

window.onload=function outerFunction(){
    var obj = document.getElementById("element");
    obj.onclick=function innerFunction(){};
    obj=null;
};
Copy the code

Setting a variable to NULL means severing the connection between the variable and the value it previously referenced. The next time the garbage collector runs, it removes these values and reclaims the memory they occupy.

Note that IE9+ does not have a DOM memory leak problem caused by circular references, either because Microsoft has optimized it or because DOM recycling has changed.

4. Memory management

4.1 When is garbage collection triggered?

The garbage collector runs periodically, and if the amount of memory allocated is very high, the collection can be difficult, so it becomes a worthwhile consideration to determine the garbage collection interval. IE6 garbage collection is based on memory allocation to run, when there are 256 variables in the environment, 4096 objects, 64K string any one of the circumstances will trigger garbage collector work, looks very scientific, do not need to call a period of time, sometimes it will not be necessary, so on demand call is not very good? But if the environment has so many variables and so on all the time, and now the script is so complex that it’s normal, then the result is that the garbage collector is always working and the browser can’t play.

Microsoft made adjustment in IE7, the trigger condition is no longer a fixed, but the dynamic modification, the initial value is the same as the IE6, if the garbage collector memory allocation amount less than 15% of the program memory, that most of the memory cannot be recycled, and the recycling trigger condition is too sensitive, at that time the street condition doubled, If more than 85% of the memory is recovered, most of the memory should have been cleaned up long ago, so put the trigger condition back. This makes garbage collection a lot more functional

4.2 Reasonable GC scheme

1. Basic programmes

The basic GC scheme for Javascript engines is simple GC: Mark and sweep:

  1. Iterate over all accessible objects.
  2. Reclaim objects that are no longer accessible.

2. GC defects

As with other languages, JS’s GC strategy is not immune to the problem that it stops responding to other operations when GC is performed for safety reasons. And Javascript GC in 100ms or even above, for the general application is ok, but for JS games, animation on coherence requirements are relatively high application, it is troublesome. This is where the new engine needs to be optimized: to avoid long pauses in response caused by GC.

3. GC optimization strategy

Uncle David mainly introduced two optimizations, and these are the most important two optimizations:

  1. Generation GC this is consistent with the Idea of Java collection strategy and is the main use of V8. The purpose is to distinguish between “temporary” and “permanent” objects; Recycle more “temporary” young generation and less “persistent generation” to reduce the number of objects to traverse each time, thus reducing the time of each GC. As shown in figure:

    It should be added here that for Tenured Generation objects, there is an additional overhead of migrating them from the Young generation to the Tenured generation, and if they are referenced, the reference reference will need to be changed. Here the main content can refer toGet the simple out of NodeThe introduction of memory, very detailed ~

  2. The idea behind incremental GC is simple: “do a little bit at a time, a little bit next time, and so on.” As shown in figure:

    This scheme, although time-consuming, but more interrupts, brings the problem of frequent context switch. Because each scheme has its own application scenarios and disadvantages, the scheme is selected according to the actual situation in practical application.

    For example, interrupt GC at a low (object /s) ratio, and simple GC at a lower rate; If a large number of objects are “alive” for a long time, generational processing has little advantage.

5. Memory leakage in Vue

JS program memory overflow, will make a section of function body forever invalid (depending on the JS code to run to which function), usually for the program suddenly stuck or abnormal procedures.

At this time we will be on the JS program memory leak investigation, to find out which objects occupied by the memory is not released. These objects are usually the ones the developer thinks are freed, but are actually still referenced by a closure or in an array.

5.1 leak

  1. DOM/BOM object leakage;
  2. A reference to a DOM/BOM object exists in a script.
  3. JS object leakage;
  4. This is usually caused by closures, such as event-handling callbacks, which lead to bidirectional references between DOM objects and objects in scripts. This is a common cause of leakage;

5.2 Code Concerns

The main focus is on various event binding scenarios, such as:

  1. In the DOMaddEventLisnerFunction and derived event listeners, such as in JqueryonFunction of the Vue component instance$onFunctions;
  2. Event listeners for other BOM objects, such as the webSocket instance’s on function;
  3. Avoid unnecessary function references;
  4. If you are usingrenderFunction to avoid binding DOM/BOM events to HTML tags;

5.3 How to Handle the fault

  1. If themounted/createdThe hook uses JS to bind events in the DOM/BOM objectbeforeDestroyDo the right should be unbound processing;
  2. If themounted/createdThe hook is initialized using a third party librarybeforeDestroyDo the corresponding destruction process (generally not used, because many times are direct globalVue.use);
  3. If used in the componentsetIntervalThat need to be inbeforeDestroyIn the corresponding destruction process;

5.4 Process addEventListener in vUE component

After calling addEventListener to add event listeners, call removeEventListener in beforeDestroy to remove the corresponding event listener. To accurately remove listeners, try not to use anonymous functions or bindings of existing functions as event listeners.

mounted() {
    const box = document.getElementById('time-line')
    this.width = box.offsetWidth
    this.resizefun = (a)= > {
      this.width = box.offsetWidth
    }
    window.addEventListener('resize'.this.resizefun)
  },
  beforeDestroy() {
    window.removeEventListener('resize'.this.resizefun)
    this.resizefun = null
  }
Copy the code

5.5 Memory leaks caused by observer mode

When using the observer mode in a SPA application, if you register the observed method with the observer and do not remove it when leaving the component, you may cause double registration and memory leak.

For example: ob.addListener(” Enter “, _func) when entering a component causes a memory leak if there is no ob.removelistener (” Enter “, _func) when leaving the component beforeDestroy

For more detailed chestnut reference: Texas Hold ’em chestnut

5.6 Memory leaks caused by context binding

Sometimes there are memory leaks when using the bind/apply/call context to bind methods.

var ClassA = function(name) {
  this.name = name
  this.func = null
}

var a = new ClassA("a")
var b = new ClassA("b")

b.func = bind(function() {
  console.log("I am " + this.name)
}, a)

b.func()    // Output: I am a

a = null           / / release a
//b = null; Release b / /
//b.func = null; / / release b. unc

function bind(func, self) {    // Simulate context binding
  return function() {
    return func.apply(self)
  }
}
Copy the code

Use Chrome dev Tool > Memory > Profiles to check the number of instances of ClassA in memory. There are two instances, A and B. Although a is set to null, the closure context self of BIND in B’s method binds a, so a is released, but b/b.func is not released, and the closure self persists and holds a reference to A.


Online posts are mostly different in depth, even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to comment out ~

Reference:

  1. Learn about javascript garbage collection and memory management
  2. App performance optimization
  3. Vue Web App Memory Leaks – Debugging and analysis
  4. Fix JavaScript memory leaks

Recommended reading:

  1. The 34 golden Rules for Optimizing web page performance at Yahoo
  2. Analyzing Javascript memory Collection (GC) with Chrome Developer Tools
  3. JS Memory leak detection method – Chrome Profiles
  4. Javascript typical memory leak and Chrome troubleshooting methods

PS: Welcome to pay attention to my official account [front-end afternoon tea], come on together

In addition, you can join the “front-end afternoon tea Exchange Group” wechat group, long press to identify the following TWO-DIMENSIONAL code to add my friend, remarks add group, I pull you into the group ~