Home  >  Article  >  Web Front-end  >  How to deal with javascript memory leaks

How to deal with javascript memory leaks

醉折花枝作酒筹
醉折花枝作酒筹Original
2021-07-15 15:30:323298browse

Processing method: 1. Assign null or reassign other values ​​after use; 2. Use modern garbage collection algorithms; 3. Be careful when saving DOM element references; 4. Pass SessionStack , playback problems in the application to avoid memory leaks and increase the memory usage of the entire application.

How to deal with javascript memory leaks

The operating environment of this tutorial: windows7 system, javascript version 1.8.5, Dell G3 computer. ,

Memory leaks are a problem that every developer will eventually face. It is the source of many problems: slow response, crashes, high latency, and other application problems.

What is a memory leak?

Essentially, a memory leak can be defined as: when the application no longer needs to occupy the memory, for some reason, the memory is not reclaimed by the operating system or the available memory pool. Programming languages ​​vary in how they manage memory. Only developers know best which memory is no longer needed and can be reclaimed by the operating system. Some programming languages ​​provide language features that help developers do this kind of thing. Others rely on developers to be clear about whether memory is needed.

JavaScript Memory Management

JavaScript is a garbage collected language. Garbage collection languages ​​help developers manage memory by periodically checking whether previously allocated memory is reachable. In other words, garbage-collected languages ​​alleviate the "memory is still available" and "memory is still reachable" problems. The difference between the two is subtle but important: only the developer knows which memory will still be used in the future, while unreachable memory is algorithmically determined and marked, and is promptly reclaimed by the operating system.

JavaScript Memory Leak

The main cause of memory leaks in garbage collected languages ​​is unwanted references. Before understanding it, you need to understand how the garbage collection language distinguishes between reachable and unreachable memory.

Mark-and-sweep

The algorithm used by most garbage collection languages ​​is called Mark-and-sweep. The algorithm consists of the following steps:

  • The garbage collector creates a list of "roots". Roots are usually references to global variables in your code. In JavaScript, the "window" object is a global variable and is treated as root. The window object always exists, so the garbage collector can check that it and all its child objects exist (i.e. are not garbage);

  • All roots are checked and marked as active (i.e. not garbage) Not garbage). All sub-objects are also checked recursively. If all objects starting from root are reachable, they are not considered garbage.

  • All unmarked memory will be treated as garbage, and the collector can now release the memory and return it to the operating system.

Modern garbage collectors have improved algorithms, but the essence is the same: reachable memory is marked, and the rest is garbage collected.

Unnecessary reference means that the developer knows that the memory reference is no longer needed, but for some reason, it is still left in the active root tree. In JavaScript, an unwanted reference is a variable that remains in the code and is no longer needed but points to a piece of memory that should be freed. Some people think this is a developer error.

To understand the most common memory leaks in JavaScript, we need to understand how references are easily forgotten.

Three types of common JavaScript memory leaks

1: Unexpected global variables

JavaScript handles undefined variables in a looser way: undefined The variable will create a new variable in the global object. In browsers, the global object is window .

The truth is:

Function foo forgot to use var internally and accidentally created a global variable. This example leaks a simple string, which is innocuous, but there are worse things.

Another unexpected global variable may be created by this:

Add 'use strict' at the beginning of the JavaScript file , can avoid such errors from occurring. Enable strict mode parsing of JavaScript to avoid unexpected global variables.

Global Variable Notes

Although we discussed some unexpected global variables, there are still some garbage generated by explicit global variables. They are defined as non-recyclable (unless defined as empty or reallocated). Care needs to be taken especially when global variables are used to temporarily store and process large amounts of information. If you must use a global variable to store a large amount of data, be sure to set it to null or redefine it after use. One major cause of increased memory consumption related to global variables is caching. Caching data is for reuse, and the cache must have an upper limit on its size to be useful. High memory consumption causes the cache to breach its upper limit because cached content cannot be reclaimed.

2: The forgotten timer or callback function

Using setInterval in JavaScript is very common. A common piece of code:

What this example illustrates: the timer associated with the node or data is no longer needed, the node object can be deleted, and the entire callback function is no longer needed. However, the timer callback function has still not been recycled (it will not be recycled until the timer stops). At the same time, someResource cannot be recycled if a large amount of data is stored.

In the case of observers, it is important to explicitly remove them once they are no longer needed (or the associated object becomes unreachable). The old IE 6 cannot handle circular references. Today, most browsers can recycle observer handlers once the observer object becomes unreachable, even without explicitly removing them.

Observer code example:

Object observer and circular reference considerations

Old versions of IE cannot detect DOM nodes and JavaScript code. Circular references between them will cause memory leaks. Today, modern browsers (including IE and Microsoft Edge) use more advanced garbage collection algorithms and can correctly detect and handle circular references. In other words, when recycling node memory, there is no need to call removeEventListener.

3: Detaching references from the DOM

Sometimes, it is useful to save the internal data structure of a DOM node. If you want to quickly update several rows of a table, it makes sense to save each row of DOM as a dictionary (JSON key-value pair) or array. At this point, there are two references to the same DOM element: one in the DOM tree and the other in the dictionary. When you decide to delete these rows in the future, you will need to clear both references.

In addition, reference issues within the DOM tree or subnodes must also be considered. Suppose your JavaScript code stores a reference to a <td> in the table. When you decide to delete the entire table in the future, you intuitively think that the GC will recycle other nodes except the saved <td>. This is not the actual situation: this <td> is a child node of the table, and the child element has a reference relationship with the parent element. Because the code retains a reference to <td>, the entire table remains in memory. Be careful when saving references to DOM elements.

4: Closures

Closures are a key aspect of JavaScript development: anonymous functions can access variables in the parent scope.

Code example:

The code snippet does one thing: every time replaceThing is called, theThing gets an array containing a large array and a new A new object of closure (someMethod). At the same time, the variable unused is a closure that references originalThing (the previous replaceThing called theThing). Are your thoughts confused? The most important thing is that once a closure's scope is created, they have the same parent scope and the scope is shared. someMethod is available through theThing, and someMethod shares the closure scope with unused, even though unused is never used , the originalThing it references forces it to remain in memory (preventing it from being recycled). When this code is run repeatedly, you will see that the memory usage continues to increase, and the garbage collector (GC) cannot reduce the memory usage. Essentially, a linked list of closures has been created, and each closure scope carries an indirect reference to a large array, causing a serious memory leak.

Meteor's blog post explains how to fix this problem. Add originalThing = null at the end of replaceThing.

Overview of Chrome Memory Profiling Tools

Chrome provides a great set of tools for detecting JavaScript memory usage. Two important tools related to memory: timeline and profiles.

Timeline

timeline can detect unwanted memory in your code. In this screenshot, we can see the steady growth of potential leaked objects. At the end of the data collection, the memory usage is significantly higher than at the beginning of the collection, and the total number of Nodes is also very high. There are various signs that there are DOM node leaks in the code.

Profiles

Profiles is a tool you can spend a lot of time focusing on, it can save snapshots, compare different snapshots of JavaScript code memory usage, and also record time distribution. Each result contains different types of lists, the ones related to memory leaks are summary lists and comparison lists.

summary (Summary) List shows the allocation and total size of different types of objects: shallow size (the total size of all objects of a specific type), retained size (shallow size plus other associated with it) object size). It also provides an idea of ​​how far an object is from its associated GC root.

Compare the comparison list of different snapshots to find memory leaks.

Example: Use Chrome to find memory leaks

There are essentially two types of leaks: leaks caused by periodic memory growth, and occasional memory leaks. Obviously, periodic memory leaks are easy to find; occasional leaks are more difficult and are generally easily ignored. Occasionally occurring memory leaks may be considered an optimization problem, while recurring ones are considered bugs that must be resolved.

Take the code in the Chrome document as an example:

When grow is executed, p nodes are created and inserted into the DOM, and a huge is assigned to the global variable. array. A steady increase in memory can be detected using the tools mentioned above.

Find out cyclically growing memory

The timeline tag is good at doing this. Open the example in Chrome, open Dev Tools, switch to timeline, check memory and click the record button, then click the The Button button on the page. Stop recording after a while and see the results:

Two signs show that there is a memory leak, Nodes (green line) and JS heap (blue line) in the picture. Nodes are growing steadily and not declining, which is a significant sign.

The memory usage of JS heap is also growing steadily. Not so easy to find due to the garbage collector. The picture shows that the memory usage rises and falls. In fact, after each drop, the size of the JS heap is larger than before. In other words, although the garbage collector continues to collect memory, the memory is still leaked periodically.

After confirming that there is a memory leak, we find the root cause.

Save two snapshots

Switch to the profiles tab of Chrome Dev Tools, refresh the page, and after the page refresh is complete, click Take Heap Snapshot to save the snapshot as a baseline. Then click the The Button button again, wait a few seconds, and save the second snapshot.

Select Summary in the filter menu, select Objects allocated between Snapshot 1 and Snapshot 2 on the right, or select Comparison in the filter menu, and then you can see a comparison list.

It is easy to find a memory leak in this example. Take a look at the (string) Size Delta Constructor, 8MB, 58 new objects. The new object is allocated, but not released, occupying 8MB.

If you expand the (string) Constructor, you will see many individual memory allocations. Selecting an individual allocation, the following retainers will draw our attention.

The allocation we have selected is part of an array associated with the x variable of the window object. This shows the complete path from the huge object to the root (window) that cannot be recycled. We've found the potential leak and where it came from.

Our example is relatively simple, only a small number of DOM nodes are leaked, which is easy to find using the snapshot mentioned above. For larger sites, Chrome also offers the Record Heap Allocations feature.

Record heap allocations Find memory leaks

Go back to the profiles tab of Chrome Dev Tools and click Record Heap Allocations. When the tool is running, pay attention to the blue bar at the top, which represents memory allocation. There is a large amount of memory allocation every second. It runs for a few seconds and then stops.

You can see the tool’s trump card in the picture above: select a certain timeline and you can see the memory allocation during this time period. Choosing a timeline as close to the peak as possible, the list below shows only three constructors: one is the most leaked (string), the next is the associated DOM allocation, and the last one is Text constructor (the text contained by the DOM leaf node).

Select a HTMLpElement constructor from the list and then select Allocation stack.

Now you know where the elements are allocated (grow -> createSomeNodes). Look carefully at the timeline in the picture and find HTMLpElement The constructor is called many times, which means that the memory has been occupied and cannot be recycled by GC. We know the exact location where these objects are allocated (createSomeNodes). Back to the code itself, let's discuss how to fix the memory leak.

Another useful feature

In the heap allocations results area, select Allocation.

This view presents a list of functions related to memory allocation, and we immediately see grow and createSomeNodes. When grow is selected, looking at the relevant object constructor, it is clear that (string), HTMLpElement and Text are leaked.

Combined with the tools mentioned above, you can easily find memory leaks.

[Recommended learning: javascript advanced tutorial]

The above is the detailed content of How to deal with javascript memory leaks. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn