A memory leak

For a definition of Javascript memory, refer to Javascript Memory Management

What is a memory leak?

A memory leak can simply be described as a memory space that should be reclaimed that is not reclaimed for some reason.

Memory leaks are a common problem in large systems for the following reasons:

  • The code itself doesn’t do a good job
  • Related dependency libraries are not handled properly
  • A browser bug

However, in daily development, the most likely cause of problems is our own code is not handled properly, if it is a mature library, memory problems are less likely.

Analysis tools

Analyze and locate leaks, usually using Chrome’s Memory panel.

Analysis steps

To prepare

Before we can analyze it, we need to know what the page does to cause a leak, such as clicking a button.

There are two kinds of analysis:

  1. The snapshot before and after leakage is recorded for comparison to find out the cause of leakage
  2. In the process of reproducing the leak snapshot, timeline is used to trace the recovery time to find the leak cause

The second method is used to determine whether there is a memory problem, and the first method is used to find the cause of the memory leak.

Before recording, one important thing is the recording environment.

  • To avoid the impact of hot updates in the development environment, because of GCRoots and hot_map cache, we may link to hot_map when we look at the reference chain, so we need to package and then create a local Nginx for analysis
  • Avoid caching and other browser plugins by using Chrome’s traceless mode
  • Avoid irrelevant variables. For example, if you already know that a file control in a pop-up box is faulty, only that file control should be left in the pop-up box during recording. Because other controls are retained, it will cause interference when we view the snapshot later
  • Keep the memory relatively small during recording, and finally control it within 80M. If it is too large, the recording overhead will be very large, affecting the efficiency

Only the steps of comparative analysis are described here.

Comparison and analysis

The steps of comparative analysis are as follows:

  • Before recording, click the trash can button on the left panel to forcibly remove it once, and then click TakeSnapshot to record a snapshot before the leak

  • Repeat leak operation

  • Click the clear button again

  • Note that some variables, such as DOM, that are not null after use may be recycled more slowly, depending on the browser, so wait a few seconds before clicking.

  • Click the record button to record a snapshot of the leak

The diagram below:

Detached HTMLDivElement now you have two snapshots. Click on the second one and select Comparison. You’ll normally search for Detached HTMLDivElement, which means separated div elements. After a leak, the DOM will also not be recycled because it is referenced by the leak source, but it is detached because it has been removed from the current Document. Div is chosen because Div is used as a container element and is used more often, so it is convenient to look at this property.

Information on the right, such as New, Deleted, and Delta, including memory size, is mainly used to determine whether there is a memory leak. If Delta (net increase) is greater than 0, there is a good chance that there is a leak.

After you expand the separate DOM list, you can view each item as shown below:

There is a reference chain from top to bottom. It should be noted that the bottom of the reference chain is not necessarily the core point of the problem because of GCRoots. What we need to do is to find useful information on this chain, mainly the following two points:

  • native_bind()

    • The mouse will prompt the function name, we can locate the function according to the function name, it needs to be noted that the function is not necessarily the source of the problem, you can go to check whether the function has a problem, the context of the function call, or whether the function is bound by the event. If we are sure that the function is working, we can try to comment out the function and record it again to see if there are any other clues.
  • InternalNode

    • Internal nodes, which may be native events or properties of the browser, will not be flagged by the mouse and will require a special version of Chrome to be viewed. At this prompt, you can probably determine that the event binding is faulty.

      • What is InternalNode
      • How to build Chrome

When viewing the reference chain, place your mouse over each layer to see if there are any hints, such as details of separated elements. When locating problems, focus on the top as much as possible.

The terminology for the Memory panel is not covered here, but you can follow this link for details.

You need to be aware of leaks when writing code

Global variable references

The implication here is not that the use of global variables in the code is a problem, but rather that a property or closure in the component references a global variable and cannot be recycled.

This problem usually happens less frequently, and I suspect that no one binds global variables to components or uses variables undeclared.

It is important to declare global variables as little as possible, because it is easy to link to properties on the window when viewing the reference chain because of GCRoots.

Second, the timer is not cleared

If you are not sure whether the timer is causing the problem, you can open developer Tools and there will be a warning in Chrome

So make it a good habit to clear setTimeout

const timer = setTimeOut(() => {
    this.resize()
    clearTimeOut(timer)
})
Copy the code

SetInterval does the same, but it is not recommended to use setInterval in code, mainly because it is not well controlled and cannot catch errors.

During normal development, take a look at the Chrome warning in the console to see what the reason is, usually related to performance, this will let you know how to write code for the browser is not a problem.

3. The event is not cleared

This is also a very common problem, form a good habit.

// mounted
window.addEventListener('resize', this.resize)
// beforeDestory
widnow.removeEventListener('resize', this.resize)
Copy the code

Note that it is best not to use on to bind events, because overwriting events and unbinding can be cumbersome.

In addition, the parameters of the binding event also need to be paid attention to. Event parameter link

The other thing to note about Passive is that if Passive is set to true, the event listener will be marked as Passive, meaning that the preventDefault function will be called to prevent the event’s default behaviour. Chrome will optimise the flow of the page based on Passive. Please refer to this link for details

If your console displays the following warning

That means you need to add Passive to prevent the default behavior of some events.

I was dealing with a memory leak that was caused by this problem. I guess the console referenced some data and could not reclaim it. Whatever the reason, this warning should be removed as much as possible.

Fourth, the console

The generated environment should not have console.log, and if it does, it should be a literal interpretation or formatted JSON string, without reference to the current instance’s data.

Dom not added to Document

Function download(url) {const a = document.createElement('a') a.href = url a.clink ()} This dom will leak if a is not nullCopy the code

Some plug-ins are used without destroying the DOM generated by the plug-in

More commonly used such as Sortable

Const sortableInstance = Sortable. Create (...) sortableInstance.destroy()Copy the code

In addition, we write some functions, if generated dom, or reference dom, remember to destroy.

Let loading = this.$loading({... , target: this.$refs.table.el})...... loading.close() loading = nullCopy the code

conclusion

Memory leaks can be very troublesome and require a lot of validation, so it’s best to make a commit before modifying the code, and then go back one by one to see if there are no leaks. These are basically the points listed above.

If you encounter other problems caused by, for example, the browser, although the probability is relatively small, but can also send a bug, such as MouseEvent on Chrome.

The most painful part of the process is locating problems based on snapshot feedback, which can lead to constant self-doubt and possibly even autism. But it also depends on your experience dealing with related issues, and if you deal with them enough, you’ll know what the core issues are.

In my opinion, whether a development has solved the memory problem is a very different point, which can prove many things, such as the way of doing things and so on. Moreover, this thing itself is also an integral thing, which requires you to have a certain understanding of the whole system knowledge.

Finally, if there are any inaccuracies or questions in the article, please point them out in the comments section. Thank ^_^

Refer to the reference

Javascript memory management

What is InternalNode

How to build Chrome

ChromeMemory term

AddeventListener parameters

Passive

Vue: Avoid memory leaks

ChromeBug report