Home  >  Article  >  Web Front-end  >  Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

coldplay.xixi
coldplay.xixiforward
2020-12-09 17:12:492585browse

javascriptThe column will discuss another important topic, memory management

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

##Related free learning recommendations: javascript(Video)

We will Another important topic discussed is memory management, which is easily overlooked by developers due to the increasing maturity and complexity of programming languages ​​used every day. We will also provide some tips on how to deal with memory leaks in JavaScript. Following these tips in SessionStack will ensure that SessionStack does not cause memory leaks and does not increase the memory consumption of our integrated web applications.


If you want to read more high-quality articles, please click on the GitHub blog. Hundreds of high-quality articles are waiting for you every year!

Overview

Programming languages ​​like C have low-level memory management primitives such as malloc() and free(). Developers use these primitives to explicitly allocate and free memory from the operating system.

JavaScript will allocate memory for objects (objects, strings, etc.) when they are created, and will "automatically" release the memory when they are no longer used. This process is called garbage collection. This seemingly "automatic" release of resources is a source of confusion because it gives JavaScript (and other high-level language) developers the false impression that they can care less about memory management. This is the idea. A big mistake.

Even when working with high-level languages, developers should understand memory management (or at least know the basics). Sometimes, there are some problems with automatic memory management (such as bugs in the garbage collector or implementation limitations, etc.) and developers must understand these problems so that they can be handled correctly (or find an appropriate solution that can be maintained with minimal effort) code).

Memory life cycle

No matter which programming language is used, the memory life cycle is the same:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Here is a brief introduction Let’s look at each stage in the memory life cycle:

  • Allocating Memory — Memory is allocated by the operating system, which allows your program to use it. In a low-level language (such as C), this is an explicitly performed operation that the developer needs to handle himself. However, in high-level languages, the system automatically assigns intrinsics for you.
  • Using Memory — This is the memory allocated before the program actually uses it. Reads and writes occur when allocated variables are used in code.
  • Release memory — Release all memory that is no longer used, making it free memory and can be reused. Like allocating memory operations, this operation also needs to be performed explicitly in low-level languages.
What is memory?

Before introducing memory in JavaScript, we will briefly discuss what memory is and how it works.

At the hardware level, computer memory is cached by a large number of triggers. Each flip-flop contains several transistors capable of storing one bit, and individual flip-flops are addressable by a unique identifier so we can read and overwrite them. Therefore, conceptually, the entire computer memory can be viewed as a huge array that can be read and written.

As humans, we are not very good at thinking and calculating in terms of bits, so we organize them into larger groups that together can be used to represent numbers. 8 bits are called 1 byte. In addition to bytes, there are also words (sometimes 16 bits, sometimes 32 bits).

A lot of stuff is stored in memory:

    All the variables and other data used by the program.
  1. Program code, including operating system code.
The compiler and operating system handle most of the memory management for you, but you still need to understand the underlying situation to have a deeper understanding of the underlying management concepts.

When compiling code, the compiler can check basic data types and calculate ahead of time how much memory they require. The required size is then allocated to the program in the call stack space, the space where these variables are allocated is called stack space. Because when functions are called, their memory is added on top of existing memory, and when they terminate, they are removed in last-in-first-out (LIFO) order. For example:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

The compiler knows immediately the required memory: 4 4 × 4 8 = 28 bytes.

This code shows the memory size occupied by integer and double-precision floating-point variables. But about 20 years ago, integer variables usually took up 2 bytes, while double-precision floating-point variables took up 4 bytes. Your code should not depend on the size of the current primitive data type.

The compiler will insert code that interacts with the operating system and allocate the number of stack bytes required to store variables.

In the above example, the compiler knows the exact memory address of each variable. In fact, whenever we write to the variable n, it will be converted internally into information like "Memory address 4127963".

Note that if we try to access x[4], the data associated with m will be accessed. This is because accessing a non-existent element in the array (which is 4 bytes larger than the last actually allocated element in the array, x[3]), may end up reading (or overwriting) some m bits. This will definitely have unpredictable results on the rest of the program.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

When functions call other functions, each function gets its own block on the call stack. It holds all local variables, but also has a program counter to remember where it is during execution. When the function completes, its memory block is used again elsewhere.

Dynamic Allocation

Unfortunately, things get a little complicated when you don't know at compile time how much memory a variable requires. Suppose we want to do the following:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

At compile time, the compiler does not know how much memory the array needs to use, because this is determined by the value provided by the user.

Therefore, it cannot allocate space for variables on the stack. Instead, our program needs to explicitly request the appropriate space from the operating system at runtime. This memory is allocated from the heap space. The difference between static memory allocation and dynamic memory allocation is summarized in the following table:

Static memory allocation Dynamic memory allocation
Size must be known at compile time Size does not need to be known at compile time
Executed at compile time Executed at runtime
Assigned to the stack Assigned to the heap
FILO (first in, last out) No specific Allocation order

To fully understand how dynamic memory allocation works, you need to spend more time on pointers, which may deviate too much from the topic of this article. I will not introduce the relevant knowledge of pointers in detail here.

Allocate memory in JavaScript

Now the first step will be explained: how to allocate memory in JavaScript.

JavaScript relieves developers from the responsibility of manually handling memory allocation - JavaScript allocates memory and declares values ​​by itself.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Certain function calls also result in memory allocation of objects:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Methods can allocate new values ​​or objects :

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Using memory in JavaScript

Using allocated memory in JavaScript means reading and writing in it, this can be done by reading or writing variables Or the value of an object property, or by passing parameters to a function.

Release memory when it is no longer needed

Most memory management problems occur at this stage

The most difficult part here is determining when the allocation is no longer needed of memory, it usually requires the developer to identify where in the program the memory is no longer needed and free it.

High-level languages ​​​​embedd a mechanism called a garbage collector, whose job is to track memory allocation and usage in order to detect any time a piece of allocated memory is no longer needed. In this case, it will automatically release this memory.

Unfortunately, this process only makes a rough estimate, because it is difficult to know whether a certain piece of memory is really needed (it cannot be solved algorithmically).

Most garbage collectors work by collecting memory that is no longer accessed, i.e. all variables pointing to it have gone out of scope. However, this is an underestimation of the set of memory space that can be collected, because at any point in a memory location, there may still be a variable in scope pointing to it, but it will never be accessed again.

Garbage Collection

Because it is impossible to determine whether some memory is really useful, the garbage collector thought of a way to solve this problem. This section explains and understands the main garbage collection algorithms and their limitations.

Memory Reference

The garbage collection algorithm mainly relies on references.

In the context of memory management, an object is said to reference another object if it has access rights to another object (either implicitly or explicitly). For example, a JavaScript object has references to its prototype (implicit reference) and property values ​​(explicit reference).

In this context, the concept of "object" is extended to a broader scope than regular JavaScript objects, and also includes function scope (or global lexical scope).

Lexical scope defines how variable names are resolved within nested functions: inner functions contain the effects of the parent function even if the parent function has returned

Reference counting garbage collection algorithm

This is the simplest garbage collection algorithm. If there is no reference to the object, the object is considered "garbage collectable", as shown in the following code:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Loops can cause problems

When it comes to When looping, there is a limit. In the example below, two objects are created that reference each other, creating a loop. After the function call will go out of scope, so they are effectively useless and can be released. However, the reference counting algorithm assumes that since each object is referenced at least once, none of them can be garbage collected.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Mark-and-sweep algorithm

This algorithm can determine whether an object can be accessed, thereby knowing whether the object is useful , the algorithm consists of the following steps:

  1. The garbage collector builds a "root" list that is used to save referenced global variables. In JavaScript, the "window" object is a global variable that can be used as the root node.
  2. The algorithm then checks all roots and their children and marks them as active (meaning they are not garbage). Any place that the roots cannot reach will be marked as garbage.
  3. Finally, the garbage collector releases all memory blocks that are not marked as active and returns that memory to the operating system.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

#This algorithm is better than the previous algorithm, because "an object is not referenced" means that the object cannot be accessed.

As of 2012, all modern browsers have mark-sweeping garbage collectors. All the improvements made in the field of JavaScript garbage collection (generational/incremental/concurrent/parallel garbage collection) over the past few years have been implementation improvements to the algorithm (mark-sweep), not improvements to the garbage collection algorithm itself, Nor is it the goal of determining whether an object is accessible.

In this article, you can read in more detail about tracking garbage collection, including the mark-sweep algorithm and its optimizations.

Loops are no longer a problem

In the first example above, after the function call returns, the two objects are no longer referenced by objects accessible from the global object. Therefore, the garbage collector will find them inaccessible.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Although there are references between objects, they are not reachable from the root node.

Counter-intuitive behavior of garbage collectors

Although garbage collectors are very convenient, they have their own set of trade-offs, one of which is non-determinism. In other words, GC is not Predictably, you can't really tell when garbage collection will occur. This means that in some cases, the program will use more memory than is actually necessary. In particularly speed-sensitive applications, short pauses may be noticeable. If no memory is allocated, most of the GC will be idle. Take a look at the following scenario:

  1. Allocate a rather large set of inners.
  2. Most (or all) of these elements are marked as inaccessible (assuming the reference points to a cache that is no longer needed).
  3. No further allocations

In these scenarios, most GCs will not proceed with collections. In other words, even if there are inaccessible references available for collection, the collector will not declare them. These are not strictly leaks, but can still result in higher than usual memory usage.

What is a memory leak?

Essentially, a memory leak can be defined as: memory that is no longer needed by the application and, for some reason, does not return to the operation system or free memory pool.

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Programming languages ​​support different memory management methods. However, whether to use a certain piece of memory is actually an undetermined question. In other words, only the developer can tell whether a piece of memory can be returned to the operating system.

Some programming languages ​​provide developers with assistance, others expect developers to have a clear understanding of when memory is no longer in use. Wikipedia has some great articles on manual and automatic memory management.

Four common memory leaks

1. Global variables

JavaScript handles undeclared variables in an interesting way: For undeclared variable, a new variable will be created in the global scope to reference it. In a browser, the global object is window. For example:

function foo(arg) {
    bar = "some text";
}

is equivalent to:

function foo(arg) {
    window.bar = "some text";
}

If bar refers to a variable within the scope of the foo function but forgets to use var to declare it, an unexpected global will be created. variable. In this example, missing a simple string wouldn't do much harm, but it would certainly be bad.

Another way to create an unexpected global variable is to use this:

function foo() {
    this.var1 = "potential accidental global";
}
// Foo自己调用,它指向全局对象(window),而不是未定义。
foo();
You can avoid this by adding "use strict" at the beginning of the JavaScript file, which will turn on A stricter JavaScript parsing mode to prevent accidental creation of global variables.

Although we are talking about unknown global variables, there is still a lot of code filled with explicit global variables. By definition, these are not collectible (unless specified as empty or reallocated). Global variables used to temporarily store and process large amounts of information are of particular concern. If you must use a global variable to store a large amount of data, be sure to specify null or reassign it when you are done.

2. Forgotten timers and callbacks

Take setInterval as an example because it is often used in JavaScript.

var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //每五秒会执行一次

The code snippet above demonstrates using a timer to reference nodes or data that are no longer needed.

The object represented by the renderer may be deleted at some point in the future, causing an entire block of code in the internal handler to become no longer needed. However, because the timer is still active, the handler cannot be collected, and its dependencies cannot be collected. This means that serverData, which stores large amounts of data, cannot be collected.

When using observers, you need to ensure that you make an explicit call to delete them after you are done using them (either the observer is no longer needed, or the object will become inaccessible).

As a developer, you need to make sure that you explicitly delete them when you are done with them (or the object will be inaccessible).

In the past, some browsers couldn't handle these situations (well IE6). Fortunately, most modern browsers now do this for you: they automatically collect observer handlers once the observed object becomes inaccessible, even if you forget to remove the listener. However, we should still explicitly remove these observers before the object is disposed. For example:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

Today’s browsers (including IE and Edge) use modern garbage collection algorithms that can detect and handle these circular references immediately. In other words, it is not necessary to call removeEventListener before a node is deleted.

Some frameworks or libraries, such as JQuery, will automatically remove listeners before disposing of nodes (when using their specific API). This is implemented by a mechanism within the library that ensures that no memory leaks occur, even when running in problematic browsers, such as... IE 6.

3. Closures

Closures are a key aspect of JavaScript development. An inner function uses variables from an outer (enclosed) function. Due to the details of how JavaScript runs, it can cause a memory leak in the following way:

Introduction to JavaScript memory management + how to deal with 4 common memory leaks

This code does one thing: every time replaceThing is called , theThing will get a new object containing a large array and a new closure (someMethod). At the same time, the variable unused points to a closure that references `originalThing.

Confused? The important thing is that once a scope with multiple closures of the same parent scope is created, this scope can be shared.

In this case, the scope created for the closure someMethod can be unused shared. unusedThere is an internal reference to originalThing. Even if unused is never used, someMethod can be passed outside the scope of replaceThing (e.g. in the global scope) by theThing to be called.

Since someMethod shares the scope of the unused closure, then the unused reference to the included originalThing will force it Keep alive (the entire shared scope between the two closures). This prevents it from being collected.

When this code is run repeatedly, you can observe that the memory usage is increasing steadily. When GC is run, the memory usage will not become smaller. Essentially, a linked list of closures is created during runtime (its root exists in the form of the variable theThing), and the scope of each closure indirectly references a large array. ,This caused a considerable memory leak.

4. Detaching references from the DOM

Sometimes, it can be useful to store DOM nodes in a data structure. Suppose you want to quickly update several rows in a table, then you can save a reference to each DOM row in a dictionary or array. In this way, there are two references to the same DOM element: one in the DOM tree and the other in the dictionary. If at some point in the future you decide to delete these rows, you will need to make both references inaccessible.

There is another issue to consider when referencing internal nodes or leaf nodes in the DOM tree. If you keep a reference to a table cell (

tag) in your code, and decide to remove the table from the DOM while keeping a reference to that specific cell, a memory leak may occur.

You might think that the garbage collector will free everything except that cell. However, this is not the case, since the cell is a child node of the table, and child nodes hold references to parent nodes, this reference to the table cell will keep the entire table in memory, so When removing a referenced node, remove its child nodes.

The above is the detailed content of Introduction to JavaScript memory management + how to deal with 4 common memory leaks. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete