preface

Regardless of the programming language, the memory life cycle is basically the same:

  1. Allocate as much memory as you need
  2. Use allocated memory (read, write)
  3. Release \ return it when it is not needed

All languageThe second part is all clear. The first and third parts are inThe underlying languageIs clear. But in the likeJavaScriptMuch of this high-level language is implicit.

Note: JavaScript is a high-level programming language that interprets execution. It is a high-level scripting language for the web.

In Chrome, V8 is limited in memory usage (around 1.4GB /1464MB for 64-bit and 0.7GB /732MB for 32-bit), mainly because V8 was originally designed for browsers and is unlikely to encounter scenarios that use large amounts of memory; A further reason is the limitations of the V8 garbage collection mechanism: cleaning up large amounts of memory garbage can be time-consuming, causing JavaScript threads to pause, causing performance and application to plummet.

JavaScript allocates memory automatically when variables (objects, strings, etc.) are created, and “automatically” frees them when they are not used. The process of release is called garbage collection.

This “automaticity” is a source of confusion and gives JavaScript developers a false sense that they can afford not to care about memory management, leading to memory leaks.

What is a memory leak

The program needs memory to run. The operating system or runtime must supply memory whenever the program requests it.

For continuously running server processes, memory that is no longer needed must be released in a timely manner. Otherwise, the memory footprint increases, which can affect system performance at best or cause process crashes at worst.

If memory that is no longer needed is not released in time, it is called a memory leak.

Common memory leaks

Poorly written JavaScript can have memory leaks that are difficult to detect and harmful.

On devices with limited memory, or in cases where functions are called many times, memory leaks can be a big problem. Memory leaks in JavaScript are mostly caused by improper references.

Here are some common memory leaks:

1. Accidentally declared global variables

Accidental declarations of global variables are the most common memory leak problem, but also the easiest to fix. The following code does not use any keywords to declare variables:

function setName() {
  name = 'yuanyuan';
}
Copy the code

At this point, the interpreter creates the variable name as a window property (equivalent to window.name = ‘Yuanyuan’). As you can imagine, properties created on the Window object do not disappear as long as the window itself is not cleaned up. This problem can be easily solved by prefixing the variable declaration with the var, let, or const keyword so that the variable goes out of scope after the function completes execution.

The forgotten timer

Timers can also quietly cause memory leaks. In the following code, the timer callback refers to an external variable via a closure:

let name = 'yuanyuan'; 
setInterval(() = > {
  console.log(name); 
}, 100);
Copy the code

The name referenced in the callback will occupy memory as long as the timer is running. The garbage collector knows this, of course, and does not clean up external variables.

3. Misused closures

Using JavaScript closures can easily cause memory leaks without even knowing it. Look at the following example:

let outer = function() { 
  let name = 'yuanyuan'; 
  return function() { 
    return name; 
  }; 
};
Copy the code

This causes the memory allocated to name to leak. The above code creates an inner closure that cannot clean up the name as long as outer exists because the closure keeps referring to it. If the content of the name is large (more than a small string), that could be a big problem.

4. Uncleared DOM references

The life cycle of a DOM element normally depends on whether it is mounted to the DOM tree, and when the element is removed from the DOM tree, it can be destroyed and recycled.

However, if a DOM element also holds a reference to it in JS, and you want to remove the element completely, you need to remove both references so that it can be recycled properly.

// Reference the DOM in the object
var elements = {
  btn: document.getElementById('btn'),}function doSomeThing() {
  elements.btn.click()
}

function removeBtn() {
  // Remove the BTN from the DOM tree
  document.body.removeChild(document.getElementById('button'))
  // At this point, however, the global variable elements retains a reference to BTN, which is still in memory and cannot be collected by GC
}
Copy the code

Although removed elsewhere, a reference to the DOM still exists in the object.

The solution is to remove the DOM node and also release the JS reference to the node:

elements.btn = null;
Copy the code

Three, garbage recycling mechanism

In some languages (such as C) memory must be freed manually, and the programmer is responsible for memory management.

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);
Copy the code

The malloc method is used to allocate memory. After using the malloc method, you must free the memory.

This is cumbersome, so most languages provide automatic memory management to ease the programmer’s burden, which is called a “garbage collector.”

JavaScript is a language that uses garbage collection, which means the execution environment is responsible for managing memory while the code executes. In languages like C and C++, tracking memory usage can be a burden for developers and a source of many problems. JavaScript takes this burden off the developer and implements memory allocation and idle resource recycling through automatic memory management. The basic idea is simple: determine which variable will no longer be used, and then free up its memory. This process is periodic, meaning that the garbage collector runs automatically at certain intervals (or at a predetermined collection time during code execution). The garbage collection process is an approximate and imperfect solution because the question of whether a block of memory is still usable is an “undecidable” problem, meaning that algorithms cannot solve it.

Let’s take the normal life cycle of a local variable in a function. Local variables in a function exist at the time the function is executed. At this point, stack (or heap) memory is allocated space to hold the corresponding value. The function uses variables internally and exits. At this point, the local variable is no longer needed and its memory can be freed for later use. Local variables are obviously no longer needed in this case, but not always. The garbage collector must keep track of which variables will be used again and which variables will not be used again in order to reclaim memory. There may be different implementations of how to mark unused variables. However, in the history of browsers, two major markup strategies have been used: tag cleanup and reference counting.

1. Mark cleaning

The most common JavaScript garbage collection strategy is mark-and-sweep. When a variable is entered into a context, such as declaring a variable inside a function, the variable is marked as existing in the context. Variables that are not in context, such as global variables declared outside a function, should logically never be freed, because they may be used as long as the code in context is running. Variables are also marked out of context when they are out of context.

There are many ways to tag variables. For example, when a variable comes into context, it reverses a bit; Or you can maintain lists of “in context” and “out of context” variables, and you can move variables from one list to the other. It’s not the implementation of the marking process that matters, it’s the strategy.

When the garbage collector runs, it marks all variables stored in memory (remember, there are many ways to mark them). It then strips out all variables in the context, and all variables referenced by variables in the context. Variables tagged after this point are deleted because they are not accessible to any variables in context. The garbage collector then does a memory cleanup, destroying all tagged values and reclaiming their memory.

By 2008, Internet Explorer, Firefox, Opera, Chrome, and Safari have all adopted tag cleaning (or variations of it) in their JavaScript implementations, with differences in how often they run garbage collection.

I’ll take a closer look at the code and compare the two strategies when I introduce reference counting.

2. Reference count

Another, less commonly used garbage collection strategy is referencecounting. The idea is to keep track of how many times each value is referenced. When you declare a variable and assign it a reference value, the number of references to that value is 1. If the same value is assigned to another variable, the number of references is increased by one. Similarly, if the variable holding a reference to that value is overwritten by another value, the number of references is reduced by one. When the number of references to a value is zero, the value can no longer be accessed, so its memory can be safely reclaimed. The next time the garbage collector runs, it frees memory for the zero reference value.

Reference counting was first adopted by Netscape Navigator 3.0, but soon ran into a serious problem: circular references. A circular reference is when object A has A pointer to object B, and object B refers to object A. Such as:

function problem() { 
	let objectA = new Object(a);let objectB = new Object(a); objectA.someOtherObject = objectB; objectB.anotherObject = objectA; }Copy the code

In this example, objectA and objectB refer to each other through their respective attributes, meaning that they both have a reference count of 2. Under the tag cleanup strategy, this is not a problem because neither object is in scope after the function ends. Under a reference-counting strategy, objectA and objectB survive the function because their references never go to zero. If the function is called multiple times, a large amount of memory will never be freed. To that end, Netscape abandoned reference counting in favor of tag cleaning in version 4.0. In fact, the problem with reference counting strategies doesn’t stop there.

In IE8 and earlier versions of IE, not all objects are native JavaScript objects. Objects in BOM and DOM are C++ implemented Component Object Model (COM) objects, and COM objects use reference counting for garbage collection. Therefore, even though the JavaScript engine of these versions of IE uses tag cleaning, the COM objects accessed by JavaScript still use reference counting. In other words, you can’t avoid the circular reference problem whenever COM objects are involved. Here is a simple example of a circular reference problem involving COM objects:

let element = document.getElementById("some_element"); 
let myObject = new Object(a); myObject.element = element; element.someObject = myObject;Copy the code

This example creates a circular reference between a DOM object (Element) and a native JavaScript object (myObject). The myObject variable has a property named Element that points to the DOM object Element, and the Element object has a someObject property that points back to the myObject. Because of circular references, the DOM element’s memory is never reclaimed, even if it has been removed from the page.

To avoid similar circular reference problems, you should break the connection between native JavaScript objects and DOM elements without making sure they are not used. For example, the circular reference created in the previous example can be cleared with the following code:

myObject.element = null; 
element.someObject = null;
Copy the code

Setting a variable to NULL actually severs the relationship between the variable and its previous reference value. When the next garbage collector runs, these values are deleted and the memory is reclaimed.

To remedy this, IE9 changed the BOM and DOM objects to JavaScript objects, which also avoids the problem of having two sets of garbage collection algorithms, as well as eliminating common memory leaks.

As you can see in retrospect, with the tag cleanup strategy, circular references are no longer an issue.

In the example above, after the function call returns, two objects are not reachable from the global object. Therefore, they will be collected by the garbage collector. Again, once divs and their event handlers are not available from the root, they will be collected by the garbage collector.

4. Memory management scheme

JavaScript variables can hold two types of values: original and reference values. The raw values are the six raw data types: Undefined, Null, Boolean, Number, String, and Symbol.

The original and reference values have the following characteristics:

  • The original value is fixed in size and therefore stored in stack memory.
  • Copying the original value from one variable to another creates a second copy of the value.
  • Reference values are objects stored in heap memory.
  • Variables that contain reference values actually contain only a pointer to the corresponding object, not the object itself.
  • Copying a reference value from one variable to another only copies Pointers, so the result is that both variables point to the same object.
  • The typeof operator determines the original typeof a value, while the instanceof operator is used to ensure the reference typeof a value.

Any variable (whether it contains a raw value or a reference value) exists in some execution context (also known as scope). This context (scope) determines the lifetime of variables and what parts of the code they can access.

The execution context can be summarized as follows:

  • Execution context is divided into global context, function context and block-level context.
  • Each time the code execution flow enters a new context, a chain of scopes is created to search for variables and functions.
  • The local context of a function or block can access variables not only in its own scope, but also in any containing context or even in the global context.
  • The global context can only access variables and functions in the global context, and cannot directly access any data in the local context.
  • The execution context of a variable is used to determine when memory is freed.

JavaScript is a programming language that uses garbage collection, so developers don’t have to worry about memory allocation and collection.

The JavaScript garbage collector can be summarized as follows:

  • Values that leave scope are automatically marked as recyclable and then deleted during garbage collection.
  • The dominant garbage collection algorithm is tag cleaning, which marks values that are not currently in use and then comes back to reclaim their memory.
  • Reference counting is another garbage collection strategy that keeps track of how many times a value is referenced.
  • JavaScript engines no longer use this algorithm, but some older versions of IE still suffer from it because JavaScript accesses non-native JavaScript objects (such as DOM elements).
  • Reference counting has problems with circular references in your code.
  • Dereferencing variables not only eliminates circular references, but also helps with garbage collection. To facilitate memory reclamation, global objects, properties of global objects, and circular references should all be dereferenced when no longer needed.

In programming environments that use garbage collection, memory management is usually not a concern for developers. However, JavaScript runs in an environment where memory management and garbage collection are special. The amount of memory allocated to browsers is typically much less than that allocated to desktop software, and even less for mobile browsers. This is more for security reasons than anything else, just to keep the operating system from crashing due to javascript-heavy web pages running out of memory. This memory limit affects not only variable allocation, but also the call stack and the number of statements that can be executed simultaneously in a thread.

● De-quote

Keeping the memory footprint to a small value can lead to better page performance. The best way to optimize memory footprint is to ensure that only necessary data is saved when executing code. If the data is no longer necessary, set it to NULL, freeing its reference. This can also be called dereferencing. This recommendation works best for global variables and properties of global objects. Local variables are automatically dereferenced when they are out of scope, as shown in the following example:

function createPerson(name) {
    let localPerson = new Object(a); localPerson.name = name;return localPerson;
}
let globalPerson = createPerson("Nicholas"); // Unreference the globalPerson value
globalPerson = null;
Copy the code

In the above code, the globalPerson variable holds the value returned by the createPerson() function call. Inside createPerson(), localPerson creates an object and adds a name attribute to it. LocalPerson is then returned as a function value and assigned to globalPerson. LocalPerson is automatically dereferenced when createPerson() completes out of context, without explicit processing. But globalPerson is a global variable and should be dereferenced manually when it is no longer needed, as the last line does.

Note, however, that dereferencing a value does not automatically cause the associated memory to be reclaimed. The key to dereferencing is to ensure that the associated value is no longer in context, so it will be collected in the next garbage collection.

Low passconstletDeclare improved Performance

The addition of these two keywords in ES6 not only helps improve the code style, but also the garbage collection process. Because both const and let are scoped by blocks (not functions), using these two new keywords may allow the garbage collector to step in and reclaim memory that should be reclaimed sooner than var would. This can happen in cases where the block scope terminates earlier than the function scope.

reference

  • JavaScript Advanced Programming (version 4)
  • Memory management
  • JavaScript Memory Leak tutorial