The vUE project I’m in charge of recently had a problem with users saying “Oh, it crashed! “Prompt.

The following performance optimization attempts were made:

  • Actively destroy the object and its children
  • Cancel the listener
  • Local search reduces component DOM rendering

Actively destroy the object and its children

Vue-cropper.js, the component instance will not be actively destroyed and will be actively destroyed by calling the destroy method. Createjs /easeljs, component instance needs to manually destroy canvas canvas, maker.stage.canvas = null; maker.stage.removeAllChildren();

Cancel the listener

Createjs /easeljs, maker.stage._eventlisteners = null; maker.stage.removeAllEventListeners(); .

Local search reduces component DOM rendering

Select component of iView, when the amount of data is too large, DOM rendering will take up a lot of memory, very performance. So we added the ability to specify the number of renderings, such as 20 for the first rendering, and then search and render from the data already loaded.

There was some benefit, but there was still a performance problem, with Javascript VM instances in Memory increasing by 100MB/ time during menu switching, and that was without data.

Therefore, an in-depth performance optimization is urgently needed to solve the problems encountered in the current project.

After solving this problem I will sharpen my skills:

  • Chrome DevTools for Memory, Performance applications
  • Vue related, javascript related, DOM related unknown memory leakage knowledge points

I’ll document the following in-depth analysis of memory leaks:

  • Snapshot memory leak analysis
  • Confusion and practice of memory leak analysis Snapshot
  • The Event Listeners of Chrome DevTools Elements analyze memory leaks

Snapshot memory leak analysis

JS heap size

Window. The performance. The memory object’s properties.

jsHeapSizeLimit: 2197815296
totalJSHeapSize: 12068848
usedJSHeapSize: 10730032
Copy the code

What is the difference between totalJSHeapSize and usedJSHeapSize? UsedJsHeapSize is the total memory: refers to the memory occupied by JS objects, including V8 internal objects. TotalJsHeapSize is the current total memory: refers to the memory occupied by the JS heap, including the free memory of any JS object.

Through the following code, you can observe the usedJSHeapSize occupation of the current document, so as to analyze whether there is a memory leak performance problem.

setInterval(() = >{
	console.log(performance.memory);
},2000)
Copy the code

As you can see, js memory usage (not including free memory) keeps increasing, and even after a period of time, it does not reach the size of the page initialization. Therefore, it can be concluded that there is a memory leak.

You can also turn on memory monitoring for JavaScript usage in Chrome’s Task Manager. But this opens up the memory usage of all tabs and even plug-ins, which is less intuitive and geeky than code’s approach.

Heap snapshot

Heap snapshot. This is the memory distribution of the current page’s JS objects and their associated DOM nodes.

  • Memory not leaked heap snapshot
  • Memory leak heap snapshot

You can create a heap snapshot before and after a memory leak. Find out the memory leaks in the two heap snapshots by comparison. It is best to analyze after one operation in order to analyze the problem.

Shallow Size

Shallow Size is the memory of the object’s own hold. Js creates space for the object itself to store data. Strings and arrays in JS have significant shallow sizes, but they are stored primarily in render memory and only expose a wrapped object on the JS heap. Render memory refers to all of the memory that monitors the page:

  • Native memory
  • The PAGE’s JS Heap Memory
  • Js heap memory of all dedicated workers

Fsi: Even a small object can indirectly hold a large amount of memory. As a result, the automatic GC program cannot handle the memory that is held indirectly.

Retained Size

This is the amount of memory that can be freed after removing objects and their dependencies, which are not accessible from GC root.

The official explanation is a mouthful. It simply means the memory size of objects and their dependent objects.

Analysis fields in Comparison

  • # New

Number of newly created objects.

  • # Deleted

Number of deleted objects.

  • # Delta

The number of total objects that have changed. The number of net objects is increased.

  • Alloc.Size

Used memory space that has been allocated.

  • Freed Size

The memory space freed by the new object.

  • Size Delta

The change frees up all of the memory. Net increase in memory space.

Constructor in Heap Snapshot

  • (Closure) Counts references to a set of objects through a function closure
  • (Array, String, number, regEXP) Lists of different object types, array, string, number, and properties of RexExp
  • (Compiled code) Anything that relates to compiled code.
  • HTMLDivElement, HTMLAnchorElement, and DocumentFragment DOM objects.
  • Dep, Observer, VNode, Watcher, VueComponent are vUE specific objects.

Property of a constructor

  • Code :: (CompileLazy builtin) V8 builtins
  • Context :: system/NativeContext V8 heap/factory.cc
  • Feedback_cell: : system V8 heap/factory. Cc
  • Map: : system/map V8 heap/factory. The cc
  • Shared [V8 heap/factory.cc] refers to SharedFunctionInfos, which is an object between a function and compiled code, and SFI has no context.
    • Function_data Function data
    • Name_or_scope_info Function name and scope information
    • Script_or_debug_info Script or debug information

Shallow size, Retained size, Freed size and Delta size, what is the unit of size?

All sizes are in bytes.

Note: Both the Shallow and Retained size columns represent data in bytes.

Confusion and practice of memory leak analysis Snapshot

Why would a menu switch leak 6MB of memory?

Story list -> Product List -> Story List increased memory usage by 6MB. Across comparisons, it is found that the Retained Size of Object mainly increased, from 26,913 (37%) to 32,933 (49%), an increase of 12%.

VueComponent also increased by 12 percent from 377 (10 percent) to 600 (22 percent).

Therefore, it is preliminarily concluded that VueComponent does not have GC.

First set of questions (theories) :

  • Is there an object that hasn’t been destroyed?
  • Is the object destroyed but the destruction fails because other objects depend on it?
  • Is the object destroyed but the destruction fails because other objects depend on its children?

This information is presented in the Summary, so how do you compare the two snapshots? Chrome DevTools provides a very handy feature called Comparison. Switch to the Snapshot you want to compare to get two diff memory usage.

After the second and first comparison, we get this comparison analysis chart.

Second set of questions (practices) : Doesn’t a component that is an instance of a component self-destruct after its parent? Is it a generic component problem? Is a common component referenced in multiple places so that the component of the current instance is not completely destroyed after the page is destroyed?

Is #Delta with the highest value (closure) the main reason?

At the end of the closure, we find familiar generic component faces and use them as a starting point for our analysis.

Analysis of the./src/components/uploadToOsscomponent

Shared is suspicious, and when clicked, it looks like this.

Component presence here indicates that the module/component closure internal variables are not null after use.

Vue does not detect that the component/module is no longer in use, so we need to do active destruction in Vue’s destroyed or beforeDestroy lifecycle.

<script>
import ALIOSS from '@/components/uploadToOss';
let commonOSS = new ALIOSS();

export default {
    beforeDestroy() {
        commonOSS = null; // This is the new code that destroys the created uploaded OSS component instance to free up closure space
    }
}
</script>
Copy the code

It is important to note that vUE cannot detect that we are not using certain modules. Only instances bound to vUE instances are destroyed along with components, and those not bound must be actively destroyed.

Set to null

Set to null

We managed to free 112 bytes, which is 0.112Kb of memory!

  • Is there an object that hasn’t been destroyed? Yes, the imported module is not destroyed.
  • Is the object destroyed but the destruction fails because other objects depend on it? No, we generally expose a class, and the newly created instance has its own context. There is no cross-reference between single-file components, so it is independent.
  • Is the object destroyed but the destruction fails because other objects depend on its children? After the object is destroyed, its children are also destroyed.
  • Doesn’t a component that is an instance automatically destroy itself with the parent component? It will be destroyed. Each introduction is independent.
  • Is it a generic component problem? Is a common component referenced in multiple places so that the component of the current instance is not completely destroyed after the page is destroyed? It isn’t. This is not due to early introduction, but to the failure to actively null the closure variables created by the component.

What does this analysis tell us? Using class Filter to search constructor, observe whether delta size is negative and Freed size is not 0, so that we can judge whether the module is completely destroyed.

After a long time of effort, only 0.012Kb was optimized, which is the same as no optimization.

Try VueComponent’s comparison to see why: in the product list snapshot, we found the Table component of the remaining undestroyed material list.

So it’s almost certain that the Table component, which switches to the material list page, has not been completely destroyed and can still be found in the product list.

So, is there a memory leak in iView’s Table component? Or is there a memory leak in vue itself?

After comparing element-UI with iView, it was found that iView did have a memory leak, and the memory usage did not decrease, while Element-UI would drop to the normal value after a while. So it’s not Vue.

I discussed it with the boss and it may be replaced with another UI framework.

The current plan is to monitor window. Performance. The memory object, continued over a period of time is greater than a certain threshold, will remind users to take the initiative to refresh the page, thus releasing memory leak out.

IView memory leak discussion:

  • Github.com/iview/iview…
  • Github.com/iview/iview…
  • www.v2ex.com/t/587573#re…

My verification method:

  • IView official website switches several times and stays on the same page. Elder-ui official website switches to observe the memory usage of the same page
  • The local project stays on the same page after switching several times, compares the number of VueComponent, and finds out the components of other pages

For example, I got this snapshot comparison by toggling foo->bar->foo->baz->foo.

As you can see from the graph, VueComponent created 612 and deleted 9 for a net gain of 603, allocated 17.296Kb of memory, freed 0.504Kb of memory (I was really excited to see how much of that was freed), and added 16.792Kb of memory. Resulting in a memory leak of 16.792Kb.

You might not think 16.792Kb is a big deal, because it ranks 19th in memory leakage in my analysis, followed by 598Kb and 506Kb, respectively.

The Event Listeners of Chrome DevTools Elements analyze memory leaks

Global event destruction in VUE to avoid listener memory leaks.

The DOM0-level event is destroyed

window.onbeforeunload = () = > {};
window.onbeforeunload = null; // Destroy, can be in vue's Destroyed lifecycle (preferably in this as there is no need to reference vue instance in beforeDestroy) or beforeDestroy.
Copy the code

The DOM2-level event is destroyed

this.foo= (e) = > {}
window.addEventListener('resize'.this.foo);
window.removeEventListener('resize'.this.foo);// Destroy, either in the beforeDestroy lifecycle of vue (references to vue instances are best destroyed in this cycle) or destroyed.
Copy the code

Before global event destruction (before memory release) :

After global event destruction (after memory release) :

Observations show that a single menu switch reduces the need for a redundant global event listener and improves performance slightly.

Summary and Reflection

After a series of analyses, we found that memory leaks can be analyzed and fixed in the following ways.

  • Listening for events at window is not unbound
  • Events tied to EventBus are not untied
  • Instances created by third-party libraries do not call the destruction function
  • Custom component/module closure internal variables are not destroyed

Front-end students in the selection of front-end UI framework, may wish to test whether there is a memory leak.

The world is muddy and clear, thanks to our inspiration!

References:

  • Juejin. Cn/post / 684490…
  • Webplatform. Making. IO/docs/apis/t…
  • Developers.google.com/web/tools/c…
  • www.bitdegree.org/learn/chrom…
  • Developers.google.com/web/tools/c…

I am looking forward to communicating with you and making progress together. Welcome to join the technical discussion group I created which is closely related to front-end development:

  • SegmentFault technosphere :ES new specification syntax sugar
  • SegmentFault column: Be a good front-end engineer while you’re still young
  • Zhihu column: Be an excellent front-end engineer while you are still young
  • Github blog: Personal blog 233 while You’re Still Young
  • Front-end development QQ group: 660634678
  • excellent_developers

Strive to be an excellent front-end engineer!