Posts Tagged ‘jquery’

Fixing jQuery memory leaks in ASP.NET

July 17th, 2009

When it comes to javascript toolkits there seem to be a plethora of options but in my opinion by far the two that stand out tall amongst others are jQuery and Prototype. Although Prototype is an aged toolkit that has proven itself and extends the existing DOM functionality acting as a proxy for the available DOM objects, I am a bigger fan of jQuery due to it’s simplicity, small footprint, and it’s powerful DOM parser.

In this write-up, I assume you are using jQuery in an ASP.NET web application and have heard some Prototype enthusiasts bash it by saying it has memory leaks and are looking for an explanation of what those are and how to fix them. I will list the two that I have come across by doing various research on the web and the solutions I found that I integrated into my applications.

Both of these memory leaks are a result of asynchronous refreshes using UpdatePanel.  When an UpdatePanel refreshes, it makes a regular request to your ASPX page and refreshes only a part of the loaded page in the browser.  During this refresh and the process of throwing away existing DOM elements is when we run into these memory leaks with jQuery.

IE Garbage Collection

I found this issue at a very thorough write-up on CodeProject which explained exactly what was going on.  To give you a summary, this is what happens.  You might have jQuery scripts that parse your DOM and attach events to various elements throughout your page.  Some of these elements are likely the ones that get thrown away when an UpdatePanel refresh happens.

The problem in IE is that it has two garbage collectors that run periodically and clean out the orphaned element objects from memory but if any of those objects have events attached to them, neither garbage collector can dispose of those objects.  Given that, if you have a page with an UpdatePanel that is constantly being refreshed with elements with events attached, the memory consumption of that IE process will keep growing.

Fortunately, before an UpdatePanel refresh happens and the old DOM elements are thrown away, the ASP.NET client side library checks each element for a dispose method and invokes that for the element to clean away any resources.  jQuery also bind to the window.unload event providing for doing clean up before the user leaves a page.  However, since jQuery doesn’t know anything about the UpdatePanel we have to make that connection between the two by taking advantage of jQuery’s plugin support and ASP.NET PageRequestManager class’ events.

Here is the jQuery plugin code:

(function($) {
    $.fn.disposable = function(cln) {
        return this.each(function() {
            var el = this;
            if (!el.dispose) {
                el.dispose = cleanup;
                $(window).bind("unload", cleanup);
            }
            function cleanup() {
                if (!el) return;
                $(el).unbind();
                $(window).unbind("unload", cleanup);
                el.dispose = null;
                el = null;
            };
        });
    };
})(jQuery);

Now, anytime you retrieve jQuery elements in your scripts and plan to attach events to them, simply call disposable on them so that they are wired up to be properly disposed as follows:

$(':submit').click(function() {
    // do something here
}).disposable();

jQuery.Data()

jQuery has a very neat feature for you to be able to store metadata on an element using the data(key, value) method on an element instance.  The problem is that if you clear contents of a DIV by doing .innerHTML = “” that contains elements that have data associated with them, the data will be orphaned and live in memory eventually causing memory leaks.

It also just so happens that when an UpdatePanel refresh happens, the new content is replaced with the old content by setting the innerHTML property of the UpdatePanel element.  This becomes tricky now because UpdatePanel doesn’t know about jQuery or it’s support for setting data on DOM elements.

Thanks to a blog by Malnotna one workaround that is clean and is abstract is as follows:

1.  Save the following as a .js file:

jQuery(function(){
    if (Sys && Sys.WebForms && !Sys.WebForms.PageRequestManager.jQueryDestroyTreeOverriden) {
        Sys.WebForms.PageRequestManager.jQueryDestroyTreeOverriden = true;
        var oldFn = Sys.WebForms.PageRequestManager.prototype._destroyTree;
        var depth = 0;
        Sys.WebForms.PageRequestManager.prototype._destroyTree = function(element) {
            depth++;
            oldFn.apply(this, [element]);
            if (--depth == 0) {
                jQuery(element).empty();
            }
        }
    }
});

2. Load it on your pages after the jQuery library.

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="MSAjaxWebFormsJQueryData.js"></script>

I hope you guys find this article helpful and keep a lookout for more posts about other jQuery/MS Ajax functionality here.