Brief introduction: Many developers may usually does not care about their maintenance page if there is a memory leak, the reason may be that to begin with simple page of memory leaks speed is very slow, before a serious caton probably get users to refresh, the problem also is hidden, but as the page is more and more complicated, especially when your page is how SAP interaction, The memory leak becomes more and more serious, until one day the user reports, “The page is stuck after a while, and I don’t know why, it hasn’t been like this before.” This article uses a few simple examples to describe how to investigate memory leaks, summarize the causes and common situations of memory leaks, and summarize how to avoid memory leaks in each case. I hope I can help you.

The author wood source and | | ali technology to the public

Many developers may usually does not care about their maintenance page if there is a memory leak, the reason may be that to begin with simple page of memory leaks speed is very slow, before a serious caton probably get users to refresh, the problem also is hidden, but as the page is more and more complicated, especially when your page is how SAP interaction, The memory leak becomes more and more serious, until one day the user reports, “The page is stuck after a while, and I don’t know why, it hasn’t been like this before.”

This article uses a few simple examples to describe how to investigate memory leaks, summarize the causes and common situations of memory leaks, and summarize how to avoid memory leaks in each case. I hope I can help you.

A simple example

For a simple example, here is the code for this example:

Code 1

The logic of code 1 is simple: clicking the “Add Date” button pushes 3000 new date objects into the dateAry array, and clicking the “Clear” button clears the dateAry. Obviously, the “add Date” operation causes a memory footprint to grow, and this logic can cause a memory leak if used in a real application (regardless of how the code logic is intentionally designed to do so). Let’s look at how to investigate why this memory growth occurs and how to find the leak.

1 heap snapshot

To avoid browser plugins, let’s open the above code in a new, traceless window in Chrome. Then go to the Memory tool “Heap Snapshot” in Chrome’s DevTools and click the record button in the upper left corner to record a Snapshot. Then click the “Add Date” button. After Garbage Collect is triggered manually, a Snapshot is recorded again, repeating the above several times to get a series of snapshots as shown in Figure 1.

Figure 1 Recording Snapshot

Figure 2 shows the Snapshot group we just got. The first one was recorded when the page was initially loaded. It’s easy to see that since the second one, each Snapshot has grown in size by about 200KB compared to the last one. Enter date in the class Filter input box to get all JS objects in Snapshot 2 that are constructed by the date constructor. The constructor shown here is related to the browser’s internal implementation and does not correspond to the JS object.

If you select a Date object, you will see the Retained Size of the selected object and the Retained Size of the Retained object in the palette below. The selected Date object is the first element of the Array (index starting from 0). And the owner of this Array is the dateAry in the system/Context Context, which is the Context of the script tag in the code, and we can see that the reserved size of this dataAry is 197KB, If we switch to Snapshot 3 and look at the memory holding and size in the same way, we can see that the retention size of dataAry in Snapshot 3 is 386KB, an increase of about 200KB compared to Snapshot 2! The same result can be obtained by comparing the dateAry in Snapshot 4 and Snapshot 5, i.e. the retained size of the dateAry in the next Snapshot is approximately 200KB.

Figure 2 Recorded Snapshot group

The add Date button, when clicked, pushes 3000 new date objects into the dateAry array. On the right side of the Date constructor in Figure 2 you can see these 3000 Date objects (Date x 3000), which correspond to the 3000 Date objects created by our loop. In summary, Memroy’s Heap Snapshot tool in Chrome DevTools can record all the memory objects at a given time, which is a “Snapshot”. The Snapshot is grouped by “constructor” and displays all the JS objects being recorded.

If the page is a page on a site that actually serves the user (the user may have clicked the “Add Date” button very frequently, and the author may want to record the number of clicks? Maybe, though I don’t know why.) As the user uses it longer, the “Add Date” button becomes more and more slow, and the overall page becomes more and more stuck, due to the increasing frequency and duration of GC in addition to the system’s memory resources, as shown in Figure 3. Because the JS execution is suspended while the GC is executing, the page will appear more and more stuck.

Figure 3 GC ratio of Performance recording

Figure 4. Chrome’s Task Manager

In the end:

Figure 5 Browser crash due to high memory usage

So, in this “real” scenario, how do you find the 3000 “haunting” Date objects? The first thing that comes to mind is: Haven’t we recorded several snapshots before? Can you compare them to find the “difference”, from the difference to find the growth of the place? The idea is very correct. Before that, let’s analyze these snapshots again: Each time the “Add Date” button is clicked and the GC is manually triggered, the Snapshot size is increased compared to the last time. If this memory growth is not “expected” (which is obviously not the case in this “real” example), then there is a strong suspicion of a memory leak.

At this point we select Snapshot 2, select “Comparison” at “Summary” as shown in Figure 2, and select Snapshot 1 at “All Objects” on the right. This is a comparison between Snapshot 1 and Snapshot 2. It is not difficult to find that +144KB in the figure is the most suspicious, so we select its Constructor Date, expand it and select any subitem to see the details. We find that it is held by the dateAry constructed by the Array constructor (i.e., a member of the dateAry), and that the dateAry is held in three places. The place with “context in ()” in Figure 6 gives us the location of the context that holds the dateAry. Click to jump to the location of the code. The whole operation is shown in Figure 6:

Figure 6. Locating the code location

One thing to note here is the “()” in “Context in () @449305” in Figure 6, which is shown as “()” because the code uses an “anonymous function “(the arrow function on line 2 in code 2) :

/ / pushDate. Write the date 】 【 addEventListener (" click ", () = > {dateCount. InnerHTML = ${+ + dateNum} ` `; for (let j = 0; j < 3000; ++j) { dateAry.push(new Date()); }});Copy the code

Code 2 anonymous function

But if we give the function a name, as shown in the following code, devTools will see the name of the function if we use the named function (add in line 2 of code 3) or if we assign the function to a variable and use that variable (the behavior in lines 10 and 18). This helps us better locate the code, as shown in Figure 7.

/ / pushDate. Write the date 】 【 addEventListener (" click ", the function of the add () {dateCount. InnerHTML = ${+ + dateNum} ` `; for (let j = 0; j < 3000; ++j) { dateAry.push(new Date()); }}); const clear = document.querySelector(".clear"); const doClear = function () { dateAry = []; dateCount.innerHTML = "0"; }; // [restore memory] clear.addeventListener ("click", doClear);Copy the code

Code 3 names the function

FIG. 7 Named function for convenient positioning

So we found something suspicious in the code, and we just had to grab the author of the code and “analyze” the memory leak.

In fact, Snapshot has a convenient Comparison entry besides “Comparison”, where you can directly see how much memory was allocated between Snapshot 1 and Snapshot 2. You can also locate the suspicious Date x 3000 in this way:

Figure 8. Snapshot comparator

The preceding file describes the method of finding memory leaks using Heap Snapshot. The advantages of this method are: Can record multiple snapshots, and then convenient pair comparison, and can see the Snapshot in the full memory, this is the following to talk about the “Allocation instrumentation on timeline” method does not have, And this method makes it easier to find memory leaks caused by the Detached Dom.

2 Allocation instrumentation on timeline

However, do you find it troublesome to record Snapshot, compare, and compare more frequently? I need to keep clicking “Add Date”, and then the mouse has to run over to click manual GC, record Snapshot, wait for the recording to finish, then operation and recording. Is there an easier way to find a memory leak? At this point we go back to the original Memory screen, and you suddenly notice that there is a radio under Heap Snapshot: “Allocation instrumentation on Timeline”, and at the end of the introductory copy under this radio says: Use this profile type to isolate memory Leaks So, we select the radio, click the Start recording button, and then focus on the page, and you’ll notice that when you click the “Add Date” button, there will be an extra heartbeat in the recorded timeline on the right side:

Figure 9 Allocation Instrumentation on timeline

As shown in Figure 9, every time we click the “Add Date” button, there is a corresponding heartbeat on the right side, and when we click the “Clear” button, all the heartbeats just appeared are “retracted”, so we draw the conclusion: Each “heartbeat” is a memory allocation, the height of which represents the amount of memory allocated, and the “heartbeat” will be changed to the height of the collected memory later in time if the allocated memory corresponding to the previous heartbeat is collected by GC. Instead of having to go back and forth and record in the Snapshot, we can just focus on the actions on the page and see which actions are suspicious in the timeline change on the right.

After a series of operations, we found that the click behavior of the button “Add Date” was suspicious, because the memory allocated by it would not be automatically recycled, that is, as long as we clicked once, the memory would grow a little. We stopped recording and got a Snapshot of timeline. At this time, if we clicked a heartbeat:

Figure 10. Click on a heartbeat

The familiar Date x 3000 appears again (Figure 11). Click on a Date object to see the holding chain.

Figure 11 finding the leak point through Timeline

The advantages of this method have been explained above. It can be very intuitive and convenient to observe the allocation and reclamation process of memory with suspicious operations, and it can be convenient to observe the memory allocated each time. Disadvantages: It takes a long time for DevTools to collect the results of long recordings, and sometimes the browser gets stuck. The detached DOM cannot be compared with the detached DOM, while heap snapshot can.

3 performance

The Performance side of DevTools also has a Memory feature, so let’s see how it works. Let’s check Memory and record a performance result:

Figure 12 Recording process for Performance

As can be seen in Figure 12, during the recording process, we continuously clicked “Add Date” button for 10 times, then clicked “Clear” button once, and then clicked “Add Date” again for 10 times, and the final result was as shown in Figure 13:

Figure 13 Recording results for Performance

In Figure 13 we can get the following information:

  • The memory trend during the whole operation: see the position below Figure 13. The memory increased continuously in the process of clicking 10 times in the first round. After clicking clear, the memory dropped like a cliff, and the memory increased continuously after clicking 10 times in the second round. This is the main purpose of this tool: to get a memory graph of a suspicious operation, and if the memory continues to rise, it is reasonable to suspect that the operation may be caused by a memory leak.
  • Memory growth: see JS Heap position to see how much memory is growing/falling up and down each ladder
  • We can also find suspicious code by locating a “ladder” in the Timeline, as shown in Figure 14:

Figure 14 uses Performance to locate the problem code

The advantage of this approach is that you can see the overall trend of memory intuitively, and you can get the stack and time of function calls during all operations. Disadvantages: No specific memory allocation details, the recording process can not see the memory allocation process in real time.

Scenarios where memory leaks occur

1 the global

JS uses token cleaning to reclaim inaccessible memory objects. The memory occupied by attributes mounted on the global object (i.e., window object in the browser, which is called the root node in the perspective of garbage collection, also called GC root) is not reclaimed because it is always accessible. This also fits the naming meaning of “global”.

The solution is to avoid using global objects to store large amounts of data.

2 Closure

We can change [code 1] a little to get a version where closures cause memory leaks:

The code 3 closure causes a memory leak

Load the above code into Chrome and record a Snapshot in timeline mode. The result is shown in Figure 15:

Figure 15 shows the recording result of the closure

We selected the index = 2 heartbeat, you can see inside the Constructor the emergence of a “(closure)”, we expand the closure, you can see the inside of the “inner ()”, The “()” behind inner() means that inner is a function, but you might ask, “Constructor has a similar Size, so why did you choose closure?” Retained Size was not used across sites, and you’ll find that no matter which one you choose, the link will remain the same.

Let’s see how inner() holds in the Retainers below: Closure () is the second item in an Array whose index starts at 0 and whose owner is the ary in the system/Context (global). And if you look at that, we can see that the ary’s Retained Size was 961KB, which is roughly five times the number of times we clicked the add date button. Each of the following five “previous in System /Context” sizes is 192KB, and they are ultimately held by an inner() closure, so we can conclude: There is an ary array in the global, and its main memory is filled by inner(). If you look at the code entry at the blue index. HTML :xx, you can see that the inner() closure holds a large object inside. And all inner() closures and the large objects they hold are held by the ARY object, which is global and cannot be reclaimed, resulting in a memory leak (if this behavior is not as expected). Going back, if you select the system/Context constructor mentioned above at this point, you will see (see Figure 16, just to get used to it) :

Figure 16 system/Context

The system/Context you selected is actually the Context object of the inner() closure, and this Context holds 192KB of memory. The blue index. HTML :xx is used to locate the code in question. You can also finally locate the problem if you select the Date constructor to look at, as shown in Figure 17. Here you leave the analysis to the reader:

Figure 17 Selects the Date constructor

3 Detached DOM

Let’s take a look at the following code and load it with Chrome:

Detached Dom

Then we recorded the two snapshots before and after clicking the “del” button by Heap Snapshot method, and the result is shown in Figure 6. Detached: Let’s compare snapshot 1 to snapshot 2 and type “detached” in the filter of Snapshot 2. We observe the “Delta” column of the filter result, where the non-zero column is as follows:

To explain the table above, two conditions must be met for a DOM object to be reclaimed: 1. The DOM is deleted from the DOM tree; 2. The DOM is not referenced by the JS object. The second point is easier to overlook. Detached HTMLButtonElement +1, as shown in the above example, means that there is a button DOM that has been removed from the component tree, but still has JS references (we don’t consider intentional).

Similarly, Detached EventListener is DOM was deleted because it is over, but there is no solution to the event, so Detached, the solution is simple: binding events can be timely solution.

The solution to this problem is simple: see code 5. When the del function is finished, the temporary variables will be collected. So both conditions are met, the DOM object will be collected, and the Detached EventListener will be removed. The table element is detached. If a TD element is detached, the entire table will not be reclaimed because it refers to the table in which it is located.

Detached CODE 5: Detached DOM solution

Figure 18 Snapshot of the Detached DOM

Monitor tool

DOM/ Event Listener leaks are easy to come by when writing tools such as round diagrams, popovers, and Toast prompts, Chrome’s DevTools includes a Performance Monitor tool that can be used to investigate whether there is a DOM/ Event Listener leak in memory. First look at code 6:

Code 6 keeps adding DOM nodes

Open the Performance Monitor panel as shown in our figure 19:

Figure 19 Opening the Performance Monitor tool

The number of DOM Nodes on the right side is the number of all DOM Nodes in memory, including the existing and detached DOM Nodes in the document and the temporary ones created during the calculation. Every time we click the “Add Date” button, After manually triggering GC, the number of DOM Nodes is + 2 because we added a Button node and a button’s text node to the document, as shown in Figure 20. If your TOAST component is in the detached state after being temporarily inserted into the Document and removed later, the number of DOM Nodes in the Performance Monitor plane will increase. With the Snapshot tool you can locate the problem. It is worth mentioning that some third party libraries toast has this problem, I do not know if you have been deceived.

Figure 20 Increasing DOM Nodes

4 console

For those of you who may not have noticed, the console print always needs to keep references in place, which is also worth noting because printing too many large objects can also cause a memory leak, as shown in Figure 21 (with code 7). The solution is not to print anything into the console, only the necessary information.

Code 7 console causes a memory leak

Figure 21 Memory leak caused by console

Three summary

This paper uses several simple examples to introduce the timing of memory leak, ways to find the leak point, and compares the advantages and disadvantages of various methods, and summarizes the attention points to avoid memory leak. Hope to be helpful to the reader. If there are any misunderstanding or writing mistakes, please leave a message to correct them.

The original link

This article is ali Cloud original content, shall not be reproduced without permission.