Moment For Technology

JS Memory leak detection method - Chrome Profiles

Posted on Sept. 23, 2022, 1:36 p.m. by Darius Wood
Category: The front end Tag: The front end

This article is adapted from the JAVASCRIPT memory leak detection method - Chrome Profiles

An overview of the

Heap Profiling is one of the most powerful JS debugging tools available in Google Chrome. Heap Profiling can take a snapshot of the current Heap memory (Heap) and generate a file describing the objects that are being used by JS at the time they are running, how much memory they occupy, the hierarchy of references, and so on. These description files provide very useful information for troubleshooting memory leaks.

Note: All the examples in this article are based on Google Chrome.

What is a heap

When JS is running, there is stack memory and heap memory. When we instantiate a class with new, the new object is stored in the heap, and the reference to the object is stored in the stack. The program finds the object through a reference in the stack. Var a = [1,2,3]; , a is the reference stored in the stack, and the heap holds the Array objects [1,2,3].

Heap Profiling

Open the tools

Open Chrome (version 25.0.1364.152m), open the site you want to monitor (take game hall as an example), press F12 to bring up the debugging tool, and click the "Profiles" TAB. You can see the following image:



As you can see, the panel monitors CPU, CSS, and memory. Select "Take Heap Snapshot" and click "Start" to Take a Heap Snapshot of the current JS, as shown below:

The right view lists the objects in the heap. Because the game lobby uses the Quark game library, class names like quark.xxx (the reference name of the Function object) are clearly visible here.

Note: Every time a snapshot is taken, GC is automatically performed so that objects in the view are reachable.

View explain

Column field explanation:

Constructor -- Class name Distance -- estimated to be the reference-level Distance from the object to the root Retained Size -- The total memory of a Retained object (including the memory of other objects referenced internally) (unit: bytes).

There is already code - closure - Array - Unknown Object - JS Object type - Unknown string - Type, and sometimes new attributes are added to the Object. The name of the attribute will appear in this Array - JS Array type CLS - unique game hall Window - the Window object Quark JS derived classes. DisplayObjectContainer - Quark engine show container classes Quark.ImageContainer -- Quark engine image class Quark.Text -- Quark engine Text class Quark.ToggleButton -- Quark engine switch button class for CLS this class name, This is because the game lobby inheritance mechanism uses the reference name "CLS" to refer to the newly created inheritance class, so objects instantiated from classes using this inheritance mechanism are placed here. For example, if a program has a ClassA class that inherits quark. Text, the new object is placed in CLS, not quark. Text.

Viewing object Content

Click on the triangle to the left of the class name to see all the objects for that class. The "@70035" after the object indicates the ID of the object (one might mistake it for the memory address, which changes after GC execution, but the object ID does not). Hovering over an object shows the object's internal properties and current values.

This view will help us identify which object it is. But the view can't keep track of who is referencing it.

View references to objects

Click on one of the objects to see the reference hierarchy of the objects as shown below:

The Object's Retaining Tree view shows which objects the Object is referenced by and the name of the reference. The object in the figure is referenced by five objects:

A CLS object's _txtContent variable; Context variable of a closure function; Self variable of the same closure function; The 0 position of an array object; A quark. Tween target variable. The quark. Text object is referenced by a variable in a closure, which is equivalent to two more references to the quark. Text object. This situation is prone to memory leaks if the closure function is not released. The quark. Text object cannot be freed either.

Expand _textContent to see the next level of reference:

Looking at the tree in reverse, we can see that one of the reference chains in the object (id@70035) looks like this:

GameListV _curV _gameListV omitted... \ | the \ | / _noticeWidget | _noticeC | _noticeV | _txtContent | | Quark. Text @ 70035 memory snapshot comparison through the comparison of the snapshot function, program can know what objects changed during operation.

I have just taken a snapshot, now I will take another one, as shown below:

Click the black solid circle button in the image to get the second memory snapshot:

Then click "Snapshot 2" in the image to switch to the second Snapshot.

Click "Summary" in the picture and a list pops up. Select "Comparison" and the result is as follows:

This view lists the object differences between the current view and the previous view. Column name field explanation: # New -- How many objects were created # Deleted -- How many objects were reclaimed # Delta -- Changed value of objects, i.e. the number of newly created objects minus the number of Deleted objects Size Delta -- Changed memory Size (bytes) Note the Delta field, especially for objects with a value greater than 0. Using quark. Tween as an example, expand the object and you can see the following figure:

In the "# New" column, if there is a ". Is a newly created object.

In the "# Deleted" column, if there are ". , represents the recycled object.

When troubleshooting problems, you should take several snapshots for comparison, so as to find out the rules.

Check for memory leaks

JS program memory overflow, will make a section of function body forever invalid (depending on the JS code to run to which function), usually for the program suddenly stuck or abnormal procedures.

At this time we will be on the JS program memory leak investigation, to find out which objects occupied by the memory is not released. These objects are usually the ones the developer thinks are freed, but are actually still referenced by a closure or in an array.

Memory leak caused by observer mode

Sometimes we need to add Observer mode to the program to decouple some modules, but if used improperly, it can cause memory leak problems.

To check for memory leaks of this type, focus on objects of the type referenced as closures and arrays.

Take Texas Hold 'em as an example:

The tester found that Texas Hold 'em game had memory overflow problem, and repeated the steps: enter the game -- exit to the partition -- enter the game -- exit to the partition again, and so repeated several times, the game would be stuck.

The troubleshooting procedure is as follows:

Open the game; Enter the first division (fastfield 5/10); After entering the system, take a memory snapshot. Exit to the previous partition interface; Enter the same partition again; After entering, take a memory snapshot again. Repeat Steps 2 to 6 until five memory snapshots are taken. Convert the views of each group to the Comparison view; Perform memory comparison analysis. After the above steps, you get the following result:

Taking a look at the last snapshot, you can see closure +1, which is what you need to focus on. The (string), (system), and (Compiled code types can be ignored because there is not much information available.

Then click on the penultimate snapshot and see that the closure type is also +1.

Then look at the next snapshot, the closure is still +1.

This means that the closure function is created every time you enter the game and is not destroyed when you exit the partition.

Expand (closure) and you'll see a lot of function objects:

The number of closures created is 49, and the number of closures recovered is 48, which means that 48 closures were released correctly and one was forgotten. The ID of each new and reclaimed function object is different, and there is no correlation to be found to determine which closure function is faulty.

Next open the Object's Retaining Tree view to see if there is an increasing array of references.

Expand the reference to each function object in "Snapshot 5" as shown below:

There's a reference to a function object called deleFunc stored in an array with an index of 4 and an object ID of @45599.

Continue to find the function object for "Snapshot 4" :

Function deleFunc = @45599; function deleFunc = @45599; function deleFunc = @45599; This object is most likely a closure that has not been released.

Continue to view the function object in "Snapshot 3" :

You can see the same function object, subscript 2. So there must be a memory leak.

There is a reference name "login_SUCCESS" below the array. A search for this keyword in the program finally locates the offending code. Because the "login_success" notification was registered when entering the game:

Ob. AddListener (" login_success _onLoginSuc); However, the notification was not removed when exiting the partition, and it was registered again when entering the game next time, resulting in increasing functions. Remove this notification when exiting a partition:

Ob. RemoveListener (" login_success _onLoginSuc); This successfully solved the memory leak problem.

Texas Hold 'em is a problem most often seen in the observer design pattern, where a global array is used to store all registered notifications. If you forget to remove notifications, the array grows larger and eventually runs out of memory.

Memory leaks caused by context binding

The context-binding function bind (also known as a delegate) is often used, and both the self-implemented bind method and the JS native bind method can leak memory.

Here's a simple example:

script type="text/javascript" var ClassA = function(name){ this.name = name; this.func = null; }; span class="hljs-keyword"var/span a = span class="hljs-keyword"new/span ClassA(span class="hljs-string""a"/span); span class="hljs-keyword"var/span b = span class="hljs-keyword"new/span ClassA(span class="hljs-string""b"/span); b.func = bind(span class="hljs-function"span class="hljs-keyword"function/spanspan class="hljs-params"()/span{! -- --/span console.log(span class="hljs-string""I am "/span + span class="hljs-keyword"this/span.name); }, a); b.func(); span class=" hlJs-literal " span class="hljs-literal"null/span; span class=" hlJs-comment " span class="hljs-comment"//b = null; span span class=" hlJs-comment" class="hljs-keyword"function/span span class="hljs-title"bind/spanspan class="hljs-params"(func, self)/span{! -- --/span span class="hljs-keyword"return/span span class="hljs-function"span class="hljs-keyword"function/spanspan class="hljs-params"()/span{! -- --/span span class="hljs-keyword"return/span func.apply(self); }; };Copy the code

In the above code, bind holds the context self through a closure, so that this in the b.fund event refers to a, not B.

First of all, let's set b = null; Comment it out, just release a. Take a look at the memory snapshot:

We can see that there are two ClassA objects, which is not what we intended, we freed A, there should be only one ClassA object B.

As you can see from the above two figures, one of these objects is B, and the other is not A, because the reference to A is already null. The second ClassA object is the context of the closure in Bind, self, which refers to the same object as a. A is freed, but because B is not freed, or because B. func is not freed, the closure self will always exist. To free self, either b=null or b.foo =null can be called.

Change the code to:

script type="text/javascript" var ClassA = function(name){ this.name = name; this.func = null; }; span class="hljs-keyword"var/span a = span class="hljs-keyword"new/span ClassA(span class="hljs-string""a"/span); span class="hljs-keyword"var/span b = span class="hljs-keyword"new/span ClassA(span class="hljs-string""b"/span); b.func = bind(span class="hljs-function"span class="hljs-keyword"function/spanspan class="hljs-params"()/span{! -- --/span console.log(span class="hljs-string""I am "/span + span class="hljs-keyword"this/span.name); }, a); b.func(); span class=" hlJs-literal " span class="hljs-literal"null/span; span class=" hlJs-literal " span class="hljs-literal"null; span class=" hlJs-comment "// release self/span span class=" hlJs-comment "// mock context binding /span span class=" hlJs-function "span  class="hljs-keyword"function/span span class="hljs-title"bind/spanspan class="hljs-params"(func, self)/span{! -- --/span span class="hljs-keyword"return/span span class="hljs-function"span class="hljs-keyword"function/spanspan class="hljs-params"()/span{! -- --/span span class="hljs-keyword"return/span func.apply(self); }; };Copy the code

Now look at memory:

You can see that there is only one ClassA object left, b, and a has been freed.

conclusion

The flexibility of JS is both a strength and a weakness, and you should pay attention to memory leaks when writing code. When the code volume is very large, you can't just review the code to troubleshoot problems. You must have some monitoring and comparison tools to help troubleshoot problems.

During the troubleshooting of memory leakage problems, we summarized the following common situations:

Closure context is not released after binding; [Fixed] Observer mode does not clear up after adding notifications The timer handler was not released in time and the clearInterval method was not called. Some controls in the view layer are repeatedly added without being removed.

Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.