How to troubleshoot where a memory leak occurs on a web page?

Today we will learn to use Devtool's Performance and Memory tools to find out where a memory leak occurs on a web page.

Today we will learn to use devtool's Performance and Memory tools to find out where memory leaks occur on web pages.

Performance panel

First, we open the browser's devtool, select the Performance (performance) panel, and then check the Memory option. If it is not checked, the memory usage will not be recorded, and the memory leak analysis will be impossible to talk about.

Then perform performance data collection:

  1. Click the "Record" button (a gray circle) in the upper left corner, or click the "Refresh" button next to it, it will reload the page and start recording, so you don't have to manually refresh and click the record button in a hurry;
  2. Perform operations on the page that may cause memory leaks, such as opening a pop-up window and then closing it;
  3. When you are almost done, click the "Record" button to end the recording, and then the result of the picture below will appear.

View memory metrics

Look at the memory usage. There are so few steps:

  1. Select the range to be analyzed;
  2. Select Main (main thread). Only if selected, the memory chart can display the information corresponding to the main thread;
  3. Look at the metrics for the memory graph.

Memory charts are line graphs that record changes in memory metrics over time. These memory indicators include: JS heap memory, number of documents, number of nodes, number of bound listeners, and GPU memory.

Click them to show or hide the corresponding line graph.

For JS Heap(11.9MB - 25.6MB), it means that in the current time range, the minimum value of JS heap memory is 11.9 MB, and the maximum value is 25.6 MB.

Hover the cursor over the line chart to see the corresponding values:

View changes in the lower limit of memory

It is normal for memory to grow. For example, when we call a function, some temporary variables will be created, resulting in an increase in memory. After the function is executed, these variables are useless, but they will not be recycled immediately, but will be recycled at an appropriate time to reduce the memory.

We don't care about the temporarily allocated short-lived memory, we pay more attention to some resident memory, and the corresponding change depends on the lower limit of memory.

If the lower limit of memory continues to rise, it means that the resident memory has become larger. This is normal in most cases, such as:

  1. Call the function and cache the result returned by the function;
  2. Create new components.

It could also be a memory leak.

When a memory leak is suspected, we can use the Memory panel to record snapshots for further investigation.

Memory panel

Open the Memory panel, click the "record button" in the upper left corner to generate a snapshot of the heap memory at the current moment. Then understand the memory distribution of JS objects through snapshots

Summary View

The snapshot results are displayed as Summary View by default.

The table items of this table are grouped based on the constructor. You can see that there are many native constructors and a bunch of closures.

Each item has the following properties:

  • Constructor: constructor. For literals without constructors, expressions like (string)​ and (array) are used.
  • Distance: The shortest path to the root node.
  • Shallow Size: The size of memory occupied by itself, excluding other object memory it introduces, in bytes.
  • Retained Size: The memory of the object itself and the object it refers to, in bytes.
  • Object Count: The number of objects, which is the number next to the Constructor name.

Above is the default Summary View view.

In addition to it, we have other views, which can be switched as follows.

Comparison View

Comparison View (Comparison View) is used to compare the changes of two snapshots.

Here I selected snapshot 3, and then set the comparison snapshot to snapshot 1.

This table represents the changes from snapshot 1 to snapshot 3. Items that have not changed will not be displayed.

The fields are:

  • Constructor: constructor.
  • #New: The number of newly added objects.
  • #Deleted: Number of objects deleted.
  • #Delta: The amount of object change overall.
  • Alloc.Size: The total memory allocated.
  • Freed Size: How much memory is freed.
  • Size Delta: Overall memory change.

Containment View

This view allows us to start from the root node and go down to view the memory occupied by various objects, as well as the location of the created code and other information.

field:

  • Object: common object or DOM node:
  • Distance: the distance to the root node.
  • Shallow Size: Object size, not counting referenced objects.
  • Retained Size: The size of the object, but the size of the object it references is also counted.

Statistics View

Ring statistics.

Percentage of various memory types in total memory.

Notes on Using the Memory Panel

Minimize the influence of distractors.

  1. Distinguish between normal memory changes.
  2. Pay attention to the impact of the hot loading logic of the packager in the development environment, etc.
  3. The code in the generated environment is obfuscated, and some constructors have strange names. If possible, package a copy of the code that has not been obfuscated locally for debugging. Or you can hover to see the object structure and guess the corresponding constructor, but the efficiency is not high.
  4. Do not have browser plug-ins, they also take up and affect memory, you can use an incognito browser.

Common memory leak causes and troubleshooting

Forgot to unbind the listener in time

A common mistake for novices and veterans is to  forget to unbind the listener in time . It results in:

  1. The object in the listener function cannot be released for a long time, such as a very large component instance.
  2. Bind a lot of useless listener functions.

How to troubleshoot?

If the listener is bound to the DOM, we can continue to perform changes to see the number of Listeners.

I wrote a pop-up window component, which will register a function to document.body when mounted, and then this function will use the variables under this component. However, the registration is not canceled when it is destroyed.

Open the Performance panel, record, then open and close the pop-up window continuously, and then end the recording. We can see the change in the number of Listeners, and if it keeps getting higher, it is forgotten.

You can also look at the changes in the number of EventListeners in the snapshot comparison of Comparison View in the Memoery panel:

Specifically which one, you can see the last few objects under EventListener.

Click on this blue link to jump to the corresponding code location:

In addition, you can also use the getEventListeners(element) method provided by the Chrome console, which will return the functions bound to an element event. This method is not a standard method, it is a tool method that comes with Chrome, and it can only be used on the console. We can write a method to search down from the root node to find the node with the largest number of binding functions. If there are too many nodes, there is a high probability that we forgot to unbind them.

If it is not a listener on the DOM, such as the event collection of the publish-subscribe library, it depends on the change in the number of objects corresponding to the constructor.

Closure

Closure is to get another function B in function A, and function B will capture variables in the scope of function A.

This leads to an implicit reference to some object, such as a DOM element. We need to set it to null when we don't need to use it.

We can see if there are any Detached elements. Detached means that it is not on the current document tree. If it continues to increase, a memory leak may have occurred.

Seriously, closures are a normal feature, there's no reason it's related to memory leaks.

Function B is held and not destroyed, so naturally the variables in function A it captures cannot be destroyed, which is no different from the fact that there are some properties in the object that cannot be destroyed. When function B is destroyed, the corresponding variables are naturally recycled.

When I have time, I will study and write another topic.

console

"What exactly did you print?"

Another common thing is to use the console to print some objects during development, merge them into the main branch and forget to remove them. These objects will not be recycled, because developers may go to the console to see the contents of these objects. This can cause performance issues when printing lots of large objects.

The troubleshooting method is very simple, to see what the DevTool console outputs and see if there are large objects.

Some helpful console for debugging is necessary, but don't abuse it.

Collection type cache explosion

We often use collection types such as objects, arrays, Maps, and Sets to cache data.

When caching a large number of objects, it will take up a lot of memory, but many of them are not needed. For the front-end, the memory is not as pure as the back-end. There is a large amount of data to be processed at every turn, and the cache is quite casual to use.

For the cache problem, we need to be a little aware, we can:

  1. Use the LRU algorithm to remove the cache that has not been used for the longest time and control the number of caches;
  2. Set the cache expiration time;
  3. For temporary caches, consider using WeakMap and WeakSet, which force recycling during GC;

There is nothing to analyze these, just look at the changes in the lower limit of memory, whether some objects have become larger and more.

end

Today, I will introduce you to the memory analysis tools provided by devtool, but you still need to do a lot of actual combat if you don’t practice fake tricks.

Guess you like

Origin blog.csdn.net/weixin_42232156/article/details/129953336