jQuery memory leak patterns and causes

22,450

Solution 1

From what I understand, memory management in javascript is accomplished by reference counting - while a reference to an object still exists, it will not be deallocated. This means that creating a memory leak in a single page application is trivial, and can trip up those of use coming from a java background. This is not specific to JQuery. Take the following code for example:

function MyObject = function(){
   var _this = this;
   this.count = 0;
   this.getAndIncrement = function(){
       _this.count++;
       return _this.count;
   }
}

for(var i = 0; i < 10000; i++){
    var obj = new MyObject();
    obj.getAndIncrement();
}

It will look normal until you look at memory usage. Instances of MyObject are never deallocated while the page is active, due to the "_this" pointer (increase the max value of i to see it more dramatically.). (In older versions of IE they were never deallocated until the program exits.) Since javascript objects may be shared between frames (I don't recommend trying this as it is seriously temperamental.), there are cases where even in a modern browser javascript objects can hang around a lot longer than they are meant to.

In the context of jquery, references are often stored to save the overhead of dom searching - for example:

function run(){
    var domObjects = $(".myClass");
    domObjects.click(function(){
        domObjects.addClass(".myOtherClass");
    });
}

This code will hold on to domObject (and all its contents) forever, because of the reference to it in the callback function.

If the writers of jquery have missed instances like this internally, then the library itself will leak, but more often it is the client code.

The second example can be fixed by explicitly clearing the pointer when it is no longer required:

function run(){
    var domObjects = $(".myClass");
    domObjects.click(function(){
        if(domObjects){
            domObjects.addClass(".myOtherClass");
            domObjects = null;
        }
    });
}

or doing the lookup again:

function run(){
    $(".myClass").click(function(){
        $(".myClass").addClass(".myOtherClass");
    });
}

A good rule of thumb is to be careful where you define your callback functions, and avoid too much nesting where possible.

Edit: As was pointed out in the comments by Erik, you could also use the this pointer to avoid the unnescessary dom lookup:

function run(){
    $(".myClass").click(function(){
        $(this).addClass(".myOtherClass");
    });
}

Solution 2

I'll contribute one anti-pattern here, which is the "mid-chain reference" leak.

One of jQuery's strengths is its chaining API, which lets you continue to change, filter, and manipulate the elements:

$(".message").addClass("unread").find(".author").addClass("noob");

At the end of that chain you have a jQuery object with all the ".message .author" elements, but that object refers back to and object with the original ".message" elements. You can get to them via the .end() method and do something to them:

 $(".message")
   .find(".author")
     .addClass("prolific")
   .end()
   .addClass("unread");

Now when used this way there are no problems with leaks. However, if you assign the result of a chain to a variable that has a long life, the back-references to earlier sets stay around and cannot be garbage collected until the variable goes out of scope. If that variable is global, the references never go out of scope.

So for example, let's say you read on some 2008 blog post that $("a").find("b") was "more efficient" than $("a b") (even though its not worthy of even thinking about such a micro-optimization). You decide you need a page-wide global to hold a list of authors so you do this:

authors = $(".message").find(".author");

Now you do have a jQuery object with the list of authors, but it also refers back to a jQuery object that is the full list of messages. You probably will never use it or even know it's there, and it's taking up memory.

Note that leaks can only occur with the methods that select new elements from an existing set, such as .find, .filter, .children etc. The docs indicate when a new set is returned. Simply using a chaining API doesn't cause a leak if the chain has simple non-filtering methods like .css, so this is okay:

authors = $(".message .author").addClass("prolific");
Share:
22,450

Related videos on Youtube

nitesh
Author by

nitesh

Java Software Engineer

Updated on February 01, 2020

Comments

  • nitesh
    nitesh about 4 years

    What are some of the standard issues or coding patterns in jQuery which lead to memory leaks?


    I have seen a number of questions related to the ajax() call or jsonp or DOM removal on StackOverflow. Most of the jQuery memory leak questions are focussed on specific issues or browsers and it would be nice to have a listing of the standard memory leak patterns in jQuery.

    Here are some related questions on SO:

    Resources on the web:

  • Raynos
    Raynos about 13 years
    Here's an example : jsfiddle.net/qTu6y/8 You can use Chrome's "Take a heapshot" in the profiler to see that every run of that block of code eats about 20MB of RAM. (Whilst testing it I did hit the "Script used too much RAM error on chrome")
  • nitesh
    nitesh about 13 years
    +1 for "those of us coming from a Java background" ... and for the lucid explanation.
  • Erik  Reppen
    Erik Reppen almost 12 years
    $(this).addClass would be better for performance in the last case since 'this' represents a core JS DOM element collection (which is what JQuery objects are typically wrapping using an adapter-style pattern) and JQ won't have to access the DOM again or parse every single DOM element on the page in the case of <IE9 (no getElementsByClassName under the hood). That last bit is also a good reason to avoid selectors that only use a className unless you're only supporting IE9 and more modern browsers in general.
  • Steve
    Steve almost 12 years
    jsfiddle.net/qTu6y/8 isn't a memory leak. It's an example of creating 100000 DIFFERENT instances of a click handler on the same object. Of course they're retained because they're all still active click handlers. If you don't believe me, put an alert in the click handler, run the code, click on the div, and see how many times the alert comes up for a single click.
  • Steve
    Steve almost 12 years
    The original "MyObject" example is also wrong. It does not create a memory leak. At least not in modern browsers like Chrome. Modern javascript engines do not use reference counting alone for garbage collection. As long as you break the reference to one of the root garbage collector objects, your object will be deleted. For pure javascript objects this just means making sure there isn't a reference chain to an object that leads back to the global scope. It gets a bit more complicated when you start mixing in DOM objects as well.
  • tofarr
    tofarr almost 12 years
    With regard to the "MyObject" example - It definitely leaked in IE6 (even after leaving the page), it leaked in IE7, and it used to leak in chrome too. I for one am happy that browsers are advancing to a point where this is no longer true
  • Luke T O'Brien
    Luke T O'Brien over 8 years
    Or use EventData: var domObjects = $(".myClass"); domObjects.click({ obj : dobObjects }, function(){ e.data.obg.addClass(".myOtherClass"); });

Related