Deep understanding of memory leak _javascript techniques in JavaScript programs

Source: Internet
Author: User
Tags event listener garbage collection script tag

Garbage collection frees us and allows us to focus on application logic rather than memory management. However, garbage collection is not magical. Understand how it works, and how to keep it in memory that was supposed to have been released long ago, enabling faster and more reliable applications. In this article, learn about a system approach to locating memory leaks in JavaScript applications, several common leak patterns, and appropriate ways to address these leaks.

First, Introduction

When dealing with scripting languages such as JavaScript, it's easy to forget that each object, class, string, number, and method needs to allocate and retain memory. The language and runtime garbage collector hides the specifics of memory allocation and deallocation.

Many features do not have to be considered for memory management, but ignore the potential for significant problems in the program. Improperly cleaned objects can have a much longer time than expected. These objects continue to respond to events and consume resources. They force browsers to allocate pages of memory from a virtual disk drive, which can significantly affect the speed of the computer (in extreme cases, which can cause the browser to crash).

A memory leak means that any object still exists after you no longer own 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 way of running. Both Firefox and legacy Internet Explorer have memory leaks, and memory leaks continue until the browser shuts down.

Many classic patterns that have led to memory leaks in the past are no longer causing leaking memory in modern browsers. However, there is a different trend now affecting memory leaks. Many people are designing WEB applications that run on a single page without a hard page refresh. In a single page like that, it is easy to keep memory that is no longer needed or irrelevant from one state of the application to another.

In this article, learn about the basic lifecycle of an object, how garbage collection determines whether an object is released, and how to evaluate the potential leak behavior. Also, learn how to use the Heap Profiler in Google Chrome to diagnose memory problems. Some examples show how to troubleshoot memory leaks caused by closures, console logs, and loops.

Second, the object life cycle

To understand how to prevent memory leaks, you need to understand the basic lifecycle of the object. When you create an object, JavaScript automatically allocates the appropriate memory for that object. From this moment on, the garbage collector constantly evaluates the object to see if it is still a valid object.

The garbage collector periodically scans objects and calculates the number of other objects that reference each object. If an object has a reference quantity of 0 (no other object references the object), or the only reference to the object is circular, the object's memory can be reclaimed. Figure 1 shows an example of the garbage collector reclaiming memory.

Figure 1. Reclaim Memory through garbage collection

It is helpful to see the actual application of the system, but the tools to provide this functionality are limited. One way to find out how much memory your JavaScript application uses is to use system tools to view the memory allocation of your browser. There are several tools available to provide you with current usage and a trend chart that depicts the amount of memory usage for a process that changes over time.

For example, if you have XCode installed on Mac OS X, you can start the instruments application and attach its active monitor tool to your browser for real-time analysis. On Windows®, you can use Task Manager. If you see a steady increase in the amount of memory usage over time as you use your application, you know there is a memory leak.

Viewing the memory footprint of a browser can only be a very cursory display of the actual memory usage of a JavaScript application. Browser data does not tell you which objects have leaked, nor does it guarantee that the data actually matches the actual memory footprint of your application. Also, because of implementation problems in some browsers, DOM elements (or alternate application-level objects) may not be freed when the corresponding elements are destroyed in the page. This is especially true for video tags, which require a more granular infrastructure to be implemented by browsers.

Multiple attempts have been made to add traces of memory allocations to the client JavaScript library. Unfortunately, all attempts are not particularly reliable. For example, the popular stats.js package is not supported due to inaccuracy. In general, trying to maintain or determine this information from the client is problematic because it introduces overhead in the application and cannot be reliably terminated.

The ideal solution is for the browser vendor to provide a set of tools in the browser to help you monitor memory usage, identify leaked objects, and determine why a particular object is still marked as reserved.

Currently, only Google Chrome (provided with HEAP profile) implements a memory management tool as its developer tool. I use Heap Profiler in this article to test and demonstrate how the JavaScript runtime handles memory.

Third, analysis heap snapshots

Before creating a memory leak, look at a simple interaction that properly collects memory. Start by creating a simple HTML page that contains two buttons, as shown in Listing 1.

Listing 1. Index.html

 
 

JQuery is included to ensure that a simple syntax for managing event bindings is appropriate for different browsers, and that the most common development practices are strictly adhered to. Adds a script tag for the leaker class and the primary JavaScript method. In a development environment, merging JavaScript files into a single file is usually a better practice. For the purposes of this example, it is easier to put logic in a separate file.

You can filter Heap Profiler to show only instances of special classes. To take advantage of this feature, create a new class to encapsulate the behavior of the leaking object, and this class can easily be 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 that should clean the leaker object and make it ready 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 ();

You are now ready to create an object, view it in memory, and then release it.

1, the index page is loaded in Chrome. Because you are loading jQuery directly from Google, you need to connect to the Internet to run the sample.
2, open the Developer tool by opening the View menu and selecting the Develop submenu. 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 your attention to the Web and select Start.
5), get another heap snapshot.
6, filter the first snapshot, look for instances of the leaker class, no instances found. To switch to the second snapshot, you should be able to find an instance, as shown in Figure 3.

Figure 3. Snapshot instance

7), return the attention to the Web, select Destroy.
8), get a third heap snapshot.
9, filter the third snapshot, find an instance of the leaker class, no instances found. When you load the third snapshot, you can also switch the profiling mode from Summary to Comparison, and compare the third and second snapshots. You will see an offset value of 1 (one instance of the leaker object was freed between the two snapshots).
Hail! Garbage collection is valid. Now is the time to destroy it.

Iv. memory leaks 1: closures

A simple way to prevent an object from being garbage collected is to set an interval or timeout for referencing the object in the callback. To see the actual application, you can 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 ();}, M
  )
 ,

 destroy:function () {
  if (this._interval!== null) {
   clearinterval (this._interval);   
  }
 },

 oninterval:function () {
  Console.log ("Interval");
 }
};

Now, when you repeat the 第1-9 step in the previous section, you should see in the third snapshot that the leaker object is persisted and that the interval will continue to run forever. So, what happened? Any local variables referenced in a closure are retained by the closure, as long as the closure exists. To ensure that a callback to the SetInterval method executes when accessing the scope of the leaker instance, you need to assign the this variable to the local variable self, which is used to trigger the oninterval from within the closure. When Oninterval is triggered, it can access any instance variable (including itself) in the leaker object. However, as long as the event listener exists, the leaker object is not garbage collected.

To work around this problem, you can trigger the Destroy method that was added to the object before emptying the stored leaker object reference by updating the Destroy button's click Handler, as shown in Listing 5.

Listing 5. Assets/scripts/main.js

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

V. Destruction of objects and ownership of objects

It is a good idea to create a standard method that is responsible for making an object eligible for garbage collection. The primary purpose of the Destroy feature is to centrally clean up the responsibility of an operation that the object completes with the following consequences:

1, to prevent its reference count down to 0 (for example, delete the problematic event listeners and callbacks, and unregister from any service).
2. Use unnecessary CPU cycles, such as intervals or animations.
The Destroy method is often the necessary step to clean up an object, but in most cases it is not enough. In theory, after destroying an associated instance, a method that retains references to the destroyed object can be invoked on its own. Because this situation can produce unpredictable results, it is important to call the Destroy method only when the object is about to be useless.

Generally speaking, the best use of the Destroy method is when an object has an explicit owner to take charge of its lifecycle. This situation often exists in layered systems, such as views or controllers in the MVC framework, or a scene diagram of a canvas rendering system.

Vi. Memory leaks 2: Console log

A less obvious way to keep an object in memory is to record it in the console. Listing 6 updates the leaker class, showing an example of this approach.

Listing 6. Assets/scripts/leaker.js

var leaker = function () {};

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

 destroy: function () {

 }  
};

The following steps can be taken to demonstrate the impact of the console.

    • Log on to the index page.
    • Click Start.
    • Go to the console and confirm that the leaking object has been tracked.
    • Click Destroy.
    • Go back to the console and type leak to record the current contents of the global variable. The value should now be empty.
    • Get 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 the console is cleaned up, the configuration file that retains the leaker should be cleared.

The impact of console logging on the overall memory profile may be an extremely significant problem that many developers have not thought of. An object that logs errors can keep a large amount of data in memory. Note that this also applies to:

1 The object that is logged during an interactive session in the console when the user types JavaScript.
2), objects recorded by the Console.log and Console.dir methods.
Vii. memory leak 3: Cycle

When two objects are referenced to each other and hold each other, a loop is generated, as shown in Figure 4.

Figure 4. Create a reference to a loop

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

Listing 7 shows a simple code example.

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 it's 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 you perform a heap analysis after the object is created and destroyed, you should see that the garbage collector detects the circular reference and frees up memory when you select the Destroy button.

However, if you introduce a third object that retains the child object, the loop can cause a memory leak. 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 off 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 an object that allows other objects to register with it and then delete itself from the registry. Although this particular class has nothing to do with the registry, it is a common pattern in the event Scheduler and notification system.

Import the class into 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>
updates the leaker object to register the object itself with the registry object (possibly for notifications about some of the not-implemented events). This creates a root alternate path from the leaker child object to be preserved, but because of the loop, the parent object is also 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 it 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 the main.js to set up 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 you perform heap analysis, you should see that each time you select the Start button, two new instances of the leaker object are created and preserved. Figure 5 shows the flow of object references.

Figure 5. Memory leaks caused by reserved references

On the surface, it looks like an unnatural example, but it's actually very common. Event listeners in a more classic object-oriented framework often follow a pattern similar to Figure 5. This type of pattern may also be associated with problems caused by closures and console logs.

Although there are several ways to solve such problems, in this case the simplest way is to update the leaker class to destroy its child objects when it is destroyed. For this example, updating the Destroy method (shown in Listing 13) is sufficient.

Listing 13. Assets/scripts/leaker.js

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

Sometimes, there is a loop between two objects that are not close enough, and one object manages the life cycle of another object. In such cases, the object that establishes the relationship between the two objects should be responsible for interrupting the loop when it is destroyed.

Conclusion

Even though JavaScript has been garbage collected, there are still a number of ways to keep unwanted objects in memory. Most browsers now have improved memory cleanup, but the tools to assess your application's memory heap are still limited (except for Google Chrome). Starting with a simple test case, it is easy to assess the potential leak behavior and determine if there is a leak.

Without testing, it is impossible to measure memory usage accurately. It is easy to make circular references occupy most areas of the graph of objects. Chrome's Heap Profiler is a valuable tool for diagnosing memory problems, and it's a good option to use it regularly at development time. When predicting the specific resources to be released in the graph of objects, set specific expectations and verify them. Any time you see unwanted results, please investigate them carefully.

You plan to clean up the object when you create it, which is much easier than porting a cleanup phase to your application later. You often plan to delete event listeners and stop the interval that you create. If you recognize the memory usage in your application, you will get more reliable and performance-efficient applications.

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.