A deep understanding of memory leaks in JavaScript programs and a deep understanding of javascript

Source: Internet
Author: User

A deep understanding of memory leaks in JavaScript programs and a deep understanding of javascript

Garbage collection frees us from allowing us to focus on application logic rather than memory management. However, garbage collection is not magical. Understand how it works, and how to make it retain the memory that should have been released a long time ago, you can achieve faster and more reliable applications. In this article, we will learn a system method to locate memory leaks in JavaScript applications, several common leak modes, and appropriate methods to solve these leaks.

I. Introduction

When processing scripting languages like JavaScript, it is easy to forget that each object, class, String, number, and method needs to be allocated and reserved. The language and runtime Garbage Collector hides the details of memory allocation and release.

Many features can be implemented without considering memory management, but ignore the major problems that may arise in the program. Objects that are improperly cleaned may take longer than expected. These objects continue to respond to events and consume resources. They can force browsers to allocate memory pages from a virtual disk drive, significantly impacting computer speed (in extreme cases, leading to browser crashes ).

Memory leakage means that any object still exists after you no longer own it or need it. In recent years, many browsers have improved the ability to reclaim memory from JavaScript during page loading. However, not all browsers have the same running mode. Both Firefox and earlier versions of Internet Explorer have experienced memory leaks, and the memory leaks continue until the browser is closed.

In the past, many classic modes that caused Memory leakage do not cause memory leakage in modern browsers. However, there is a different trend that affects memory leakage. Many people are designing Web applications that run on a single page without a hard page refresh. In such a single page, from one state of the application to another, it is easy to retain memory that is no longer needed or irrelevant.

This article describes the basic lifecycle of an object, how to determine whether an object is released, and how to assess potential leakage behavior. In addition, learn how to use Heap Profiler in Google Chrome to diagnose memory problems. Some examples show how to solve the memory leakage caused by closures, console logs, and loops.

Ii. Object Lifecycle

To learn how to prevent memory leakage, you need to understand the basic lifecycle of the object. When an object is created, JavaScript automatically allocates the appropriate memory for the object. From this moment on, the garbage collector will constantly evaluate the object to see if it is still valid.

The garbage collector periodically scans objects and calculates the number of other objects that reference each object. If the number of references to an object is 0 (no other object has referenced this object), or the unique reference to this object is cyclic, the memory of this object can be recycled. Figure 1 shows an example of memory Recycle by the garbage collector.

Figure 1. Reclaim memory through garbage collection

The actual application of the system is helpful, but the tools that provide this function are limited. One way to learn how much memory your JavaScript Application occupies is to use system tools to view the browser's memory allocation. There are multiple tools to provide you with the current use, and describe a process's memory usage trend chart over time.

For example, if XCode is installed on Mac OSX, you can start the Instruments application and append its activity monitor tool to your browser for real-time analysis. In Windows®You can use the task manager. If you find that the memory usage increases steadily over time during your application usage, you will know that there is a memory leak.

Observe that the memory usage of the browser can only roughly display the actual memory usage of JavaScript applications. Browser data does not tell you which objects have been leaked, nor does it guarantee that the data exactly matches the real memory usage of your application. In addition, due to implementation problems in some browsers, DOM elements (or alternative application-level objects) may not be released when the corresponding elements are destroyed on the page. This is especially true for video tagging, which requires the browser to implement a more refined infrastructure.

People have tried to add a trace of memory allocation in the client's JavaScript library for many times. Unfortunately, all attempts are not particularly reliable. For example, the popular stats. js package cannot be supported due to inaccuracy. Generally, an attempt to maintain the information from the client or determine whether there is a problem because it will introduce overhead in the application and cannot be reliably terminated.

The ideal solution is that browser vendors provide a set of tools in the browser to help you monitor memory usage, identify leaked objects, and determine why a special object is still marked as retained.

Currently, only Google Chrome (with Heap Profile) implements a memory management tool as its developer tool. I used Heap Profiler in this article to test and demonstrate how to handle memory during JavaScript runtime.

3. Analyze heap snapshots

Before creating a memory leak, check a simple interaction that properly collects the memory. First, create a simple HTML page containing two buttons, as shown in Listing 1.

Listing 1. index.html

JQuery is included to ensure that a simple syntax for event management binding is applicable to different browsers and strictly follows the most common development practices. Add script markup for the leaker class and main JavaScript methods. In the development environment, it is usually better to merge JavaScript files into a single file. For the purpose of this example, it is easier to put the logic in a separate file.

You can filter Heap Profiler to display only instances of special classes. To use this function, create a new class to encapsulate the behavior of the leaked object, and this class is easily found in Heap Profiler, as shown in Listing 2.

Listing 2. assets/scripts/leaker. js

var Leaker = function(){};Leaker.prototype = { init:function(){ } };

Bind the Start button to initialize the Leaker object and assign it to a variable in the global namespace. You also need to bind the Destroy button to a method for clearing the Leaker object and prepare it for garbage collection, as shown in listing 3.

Listing 3. assets/scripts/main. js

$("#start_button").click(function(){ if(leak !== null || leak !== undefined){  return; } leak = new Leaker(); leak.init();});$("#destroy_button").click(function(){ leak = null;});var leak = new Leaker();

Now you are ready to create an object, view it in memory, and release it.

1) load the index page in Chrome. Because you directly load jQuery from Google, you need to connect to the Internet to run this example.
2) Open the developer tool by opening the View menu and selecting the Develop sub-menu. Select the Developer Tools command.
3) Go to the Profiles tab and get a heap snapshot, as shown in figure 2.

Figure 2. Profiles Tab

4) return the attention to the Web and select Start.
5). Get another heap snapshot.
6) filter the first snapshot, find the Leaker instance, and cannot find any instance. Switch to the second snapshot, you should be able to find an instance, as shown in 3.

Figure 3. snapshot instance

7) return the attention to the Web and select Destroy.
8). Obtain the third heap snapshot.
9) filter the third snapshot, find the Leaker instance, and cannot find any instance. When loading the third snapshot, you can also switch the analysis mode from Summary to Comparison and compare the third and second snapshots. You will see the offset value-1 (an instance of the Leaker object is released between two snapshots ).
Long live! Garbage collection is effective. It's time to destroy it.

Iv. Memory leakage 1: Closure

One simple way to prevent an object from being recycled by garbage collection is to set an interval or timeout when the object is referenced in the callback. To view the actual application, update the leaker. js class, as shown in Listing 4.

Listing 4. assets/scripts/leaker. js

var Leaker = function(){};Leaker.prototype = { init:function(){  this._interval = null;  this.start(); }, start: function(){  var self = this;  this._interval = setInterval(function(){   self.onInterval();  }, 100); }, destroy: function(){  if(this._interval !== null){   clearInterval(this._interval);     } }, onInterval: function(){  console.log("Interval"); }};

Now, when you repeat Step 1st-9 in the previous section, you should see in the third snapshot that the Leaker object is persistent and the interval will continue to run forever. So what happened? Any local variables referenced in a closure will be retained by the closure, and will be retained as long as the closure exists. To ensure that the setInterval method callback is executed within the range of the access to the Leaker instance, You need to assign this variable to the local variable self, which is used to trigger onInterval from the closure. When onInterval is triggered, it can access any instance variable (including its own) in the Leaker object ). However, as long as the event listener exists, the Leaker object will not be reclaimed.

To solve this problem, you can trigger the destroy method added to the object before clearing the reference of the stored leaker object by updating the click handler of the Destroy button, as shown in listing 5.

Listing 5. assets/scripts/main. js

$("#destroy_button").click(function(){ leak.destroy(); leak = null;});

5. Destroy object and object ownership

A good practice is to create a standard method to qualify an object for garbage collection. The main purpose of the destroy function is to perform operations that have the following consequences:

1. Stop its reference count from dropping to 0 (for example, deleting problematic Event Listeners and callbacks and canceling registration from any service ).
2. Use unnecessary CPU cycles, such as intervals or animations.
The destroy method is often a necessary step to clean up an object, but in most cases it is not enough. Theoretically, after the related instance is destroyed, other objects referenced by the destroyed object are retained. This situation may produce unpredictable results, so it is important to call the destroy method only when the object is about to be unavailable.

In general, the destroy method is best used when an object has a clear owner responsible for its lifecycle. This situation often exists in a layered system, such as a view or controller in the MVC framework, or a canvas that presents the scenario diagram of the system.

6. Memory leakage 2: Console logs

One way to keep an object in memory is to record it to the console. In Listing 6, the Leaker class is updated and an example of this method is displayed.

Listing 6. assets/scripts/leaker. js

var Leaker = function(){};Leaker.prototype = { init:function(){  console.log("Leaking an object: %o", this); }, destroy: function(){ }  };

To demonstrate the impact of the console, follow these steps.

  • Log on to the index page.
  • Click Start.
  • Go to the console and confirm that the Leaking object has been tracked.
  • Click Destroy.
  • Return to the console and type the leak to record the current content of the global variable. This value should be blank at this moment.
  • Obtain another heap snapshot and filter the Leaker object. You should leave a Leaker object.
  • Go back to the console and clear it.
  • Create another heap configuration file. After clearing the console, the configuration files that keep the leaker should be cleared.

The impact of console logging on the overall memory configuration file may be an extremely important issue that many developers did not think. Objects with incorrect records can store a large amount of data in the memory. Note that this also applies:

1) Objects recorded during an interactive session in the console when you type JavaScript.
2) Objects recorded by the console. log and console. dir methods.
7. Memory leakage 3: loop

When two objects are referenced and retained, a loop is generated, as shown in figure 4.

Figure 4. Create a circular reference

A blue root node in the figure is connected to two green boxes, showing a connection between them.

Listing 7 shows a simple sample code.

Listing 7. assets/scripts/leaker. js

var Leaker = function(){};Leaker.prototype = { init:function(name, parent){  this._name = name;  this._parent = parent;  this._child = null;  this.createChildren(); }, createChildren:function(){  if(this._parent !== null){   // Only create a child if this is the root   return;  }  this._child = new Leaker();  this._child.init("leaker 2", this); }, destroy: function(){ }};

The instantiation of the Root object can be modified, as shown in listing 8.

Listing 8. assets/scripts/main. js

leak = new Leaker(); leak.init("leaker 1", null);

If a heap analysis is performed after the object is created and destroyed, the garbage collector detects this circular reference and releases the memory when you select the Destroy button.

However, if the third object that retains the sub-object is introduced, this loop will cause memory leakage. For example, create a registry object, as shown in listing 9.

Listing 9. assets/scripts/registry. js

var Registry = function(){};Registry.prototype = { init:function(){  this._subscribers = []; }, add:function(subscriber){  if(this._subscribers.indexOf(subscriber) >= 0){   // Already registered so bail out   return;  }  this._subscribers.push(subscriber); }, remove:function(subscriber){  if(this._subscribers.indexOf(subscriber) < 0){   // Not currently registered so bail out   return;  }    this._subscribers.splice(     this._subscribers.indexOf(subscriber), 1    ); }};

The registry class is a simple example of letting other objects register with it and then delete their own objects from the registry. Although this special class is unrelated to the registry, it is a common pattern in the event scheduler and notification system.

Import this class to the index.html page and place it before leaker. js, as shown in listing 10.

Listing 10. index.html
<Script src = "assets/scripts/registry. js" type = "text/javascript"
Charset = "UTF-8"> </script>
Update the Leaker object to register the object itself with the Registry object (may be used for notifications about some unimplemented events ). This creates a backup path for the root node from the leaker sub-object to be retained, but because of this loop, the parent object will also be retained, as shown in listing 11.

Listing 11. assets/scripts/leaker. js

var Leaker = function(){};Leaker.prototype = { init:function(name, parent, registry){  this._name = name;  this._registry = registry;  this._parent = parent;  this._child = null;  this.createChildren();  this.registerCallback(); }, createChildren:function(){  if(this._parent !== null){   // Only create child if this is the root   return;  }  this._child = new Leaker();  this._child.init("leaker 2", this, this._registry); }, registerCallback:function(){  this._registry.add(this); }, destroy: function(){  this._registry.remove(this); }};

Finally, update main. js to set the registry and pass a reference to the Registry to the leaker parent object, as shown in listing 12.

Listing 12. assets/scripts/main. js

 $("#start_button").click(function(){ var leakExists = !(  window["leak"] === null || window["leak"] === undefined ); if(leakExists){  return; } leak = new Leaker(); leak.init("leaker 1", null, registry);});$("#destroy_button").click(function(){ leak.destroy(); leak = null;});registry = new Registry();registry.init();

Now, when performing heap analysis, you should see that each time you select the Start button, two new instances of the Leaker object will be created and retained. Figure 5 shows the object reference process.

Figure 5. Memory leakage caused by reserved references

On the surface, it looks like an unnatural example, but it is actually very common. The event listeners in a more classic Object-Oriented Framework often follow a pattern similar to Figure 5. This type of mode may also be associated with problems caused by closures and console logs.

Although there are multiple ways to solve this problem, the simplest way is to update the Leaker class to destroy its sub-objects when it is destroyed. In this example, update the destroy method (as shown in listing 13.

Listing 13. assets/scripts/leaker. js

destroy: function(){ if(this._child !== null){  this._child.destroy();    } this._registry.remove(this);}

Sometimes, a loop exists between two objects that are not closely related. One object manages the lifecycle of another object. In this case, the objects that establish the relationship between the two objects should be responsible for interrupting the loop when they are destroyed.

Conclusion

Even if JavaScript has been recycled, there are still many ways to keep unwanted objects in the memory. Currently, most browsers have improved the memory cleanup function, but there are still limited tools to evaluate your application memory heap (except Google Chrome ). Starting from a simple test case, it is easy to assess potential leakage behavior and determine whether there is a leakage.

Without testing, it is impossible to use the accurate amount of memory. It is easy to make loop reference occupy most of the area in the object graph. Chrome's Heap Profiler is a valuable tool for diagnosing memory problems. It is also a good option to use it regularly during development. Set specific expectations when you want to release specific resources in the prediction object graph, and then verify. When you see unwanted results, please take a closer look.

It is much easier to schedule the object cleanup when creating an object than porting a cleanup phase to an application later. It is often necessary to delete the event listener and stop the interval you created. If you realize the memory usage in your application, you will get more reliable and more performance applications.

Articles you may be interested in:
  • Plugin: detects javascript Memory leakage
  • Prevents Memory leakage caused by dynamic loading of JavaScript
  • Memory leakage caused by javascript removeChild
  • Detailed analysis of javascript garbage collection mechanism and Memory leakage
  • JavaScript Memory leakage
  • Memory leakage in Node. js

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.