JavaScript Memory Management

|   Jun 16, 2011

In the Front-End Engineering community, long-standing assumptions about the life of a page are being challenged as full-page refreshes and traditional Ajax patterns are replaced with Ajax Navigation and client-side frameworks like Backbone and SproutCore. While these new technologies enable greater performance and code re-use, the benefits come at a cost – added complexity. With these new patterns, JavaScript memory management becomes an even more critical aspect of the development process. In order to successfully apply these new technologies, Front-End Engineers will need to understand and deliberately manage the individual lifecycles and memory footprints of the components (DOM Elements, JavaScript Objects, client-side cache) that make up their applications.

There are two main contributors to memory issues in JavaScript:

  • Orphaned JS/DOM objects.
  • Failed garbage collection in Internet Explorer due to circular references between JS objects.

Orphaned Components

Often one of the most overlooked aspects of memory management, orphaned components are related to (but not registered as a child of) a “step-parent” that is no longer in the page. For example: Flash objects, cache records, notifications, conditional UI controls, and other related elements that were created alongside the associated element and abandoned. As page activity increases, these remnant components grow in number, resulting in ballooning memory usage.

Solutions:

Every JS Object and DOM element needs an “exit strategy.” For JS objects, this is an explicit nullification (object = null) when the reference is no longer needed. For DOM elements, calling a library method like jQuery.remove() is the most convenient way to ensure all attributes, event handlers, expando properties, and child elements are destroyed. For “dependent” components that need to be cleaned up as a result of a related state change, register the operation in a destroy callback:

  • SproutCore: willDestroyLayer
  • Backbone View: “remove” method will use jQuery/Zepto’s remove by default, but you can override the method.
  • Backbone Model/Collection: bind to “remove” event (called after a successful destroy).
  • Implement your own “remove” binding. There are many approaches, but here is a basic example:

The “exit strategy” pattern seems straightforward, but the trick is ensuring that the reasoning behind the destruction of Flash objects, client-side cache, and other associated components is clear to your development team. Code can easily be removed under good intentions during a refactor by another team member, so it’s a good idea to include documentation:

// Remove the message from the Cache
// Must explicitly null out this reference for IE GC
messagesCache[messageID] = null;
delete messagesCache[messageID];

Circular References

JavaScript Objects and DOM elements that store references to one another cause Internet Explorer’s garbage collector to not reclaim memory, resulting in memory leaks.

Solutions:

Academic Solution: “Use closures sparingly.” This is the takeaway from the MSDN Memory Leaks Page and numerous other articles on the subject. It’s wrong. Closures are an important feature of the language and re-architecting around them results in un-maintainable and disorganized code:

// MS example of a memory leak "caused by closure"

hookup(document.getElementById('menu'));

function hookup(element) {
  // Reference to #menu element available within 
  // mouse scope via closure
  function mouse (){}
  
  // mouse attached to #menu element via event.
  element.attachEvent( "onmouseover", mouse);
}

// --------------------------------------------------------
// MS proposed solution: Don't use closures
// (safe but not convenient or practical)

hookup(document.getElementById('menu'));

// No access to #menu element within 
// mouse scope (except via `this` keyword)
function mouse () {}

function hookup(element) {
   element.attachEvent( "onmouseover", mouse);
}

//-------------------------------------------------
// Solution 2: Create another closure 
// notice: element argument has been removed
hookup();

function hookup() {
  // Mouse has no access to element in 
  // it's scope (except through `this`)
  function mouse () {}
  
  (function(){
    // Reference creation within closure to contain scope.
    var element = document.getElementById('menu');
    element.attachEvent( "onmouseover", mouse);
  })();
  
}

Practical Solution: Embrace circular references when they improve code clarity with the expectation that you’ll explicitly nullify JS objects at the end of their lifecycle.

  • Call jQuery.remove() on an element and jQuery will explicitly nullify the properties and events for the element, designating the memory for reclamation*.
  • Warning: relying on a library to nullify all references on destroy doesn’t mean you’re home free. We ran into a jQuery bug where event handlers were not being removed correctly and were able to apply a patch. The jQuery Community is very proactive in reporting and patching memory leak issues, but it’s a good lesson to stay diligent.

Speaking of staying diligent; How do you stay on top of memory issues and make sure they don’t crop up again? This is an open question, and one that I’m interested in learning more about from the JS community. One thing is for certain, you will need solid tools.

Detection & Measurement

A good starting point to apply these concepts is to benchmark the amount of memory that is allocated for a set of core components in your application. Once you have a baseline, you will be one step closer to understanding how the footprint changes over time and isolating potentially expensive memory issues during code reviews.

At Socialcast, we used a combination of IEJSLeaksDetector, Sieve, and an activity-generating bookmarklet called Hammer to measure memory usage and the total number of DOM elements under different application loads. Measurements were taken on page load, after individual actions, and during intensive usage (hundreds of messages created from JSON objects) to isolate problem areas.

Generating activity with the Hammer Bookmarklet

Generating activity with the Hammer Bookmarklet

In our core application, a message can go from being simple (a brief status update) to complex (videos, polls, link previews, or other attachments). Comparing the baseline memory footprint to a message that contained a Flash object revealed an exponential increase in memory usage and prompted projects to break reliance on Flash as a dependency for certain features.

Detection Tools:

  • IEJSLeaksDetector
    • Highlight: stack traces for memory leaks.
  • Sieve
    • Highlight: memory usage report and live deltas as memory is reclaimed by the Garbage Collector.
  • Google Chrome Memory Heap Profiler
    • Highlight: Integrated within Chrome (OS platform-agnostic) and interactive stack traces.

After using Sieve and IEJSLeaksDetector without any good alternatives, I’m very excited about Chrome’s new Memory Heap Profiler. Google’s move to add this as a core developer tool reinforces that memory management in JavaScript will be a crucial focal point for Front-End Engineers in the coming years.

*Notes on garbage collectors:

Browser garbage collectors do not run immediately after elements are removed or references are nullified. There are many articles that claim to have found ways to trigger them, but none worked definitively in my testing. The key concept to understand is that removing/nullifying objects *designates* the memory for reclamation, and it’s up to the browser to determine when that occurs.

Resources:

These are a sampling of the better resources for understanding memory leaks. I recommend starting with the IBM article for the clearest examples.

Comments

  • I think the circular references issue that you pointed out for IE is quite old and it was for IE6 mainly ( the linked article is from 2005 ) and to me that article is no longer valid, since IE8 there have been a lot of improvements in IE engine itself, in fact in IE9 they have re-architected how the JS engine integrates with the browser(http://blogs.msdn.com/b/ie/archive/2010/08/04/html5-modernized-fourth-ie9-platform-preview-available-for-developers.aspx).

    If I remember correctly Microsoft released a Windows update(http://support.microsoft.com/kb/929874/) later to fix this garbage collection issue, although I think it couldn’t eliminate it completely.

    Anyway your article does offer a look into potential issues that we’ve already started observing in Ajax heavy web applications, anyway thanks for the write up.

    Cheers!

    Commented on June 17, 2011 at 11:04 am
  • Thanks for this great writeup, it’s easy to forget about some of the circular reference gotchas. Just today I had a colossal memory leak in IE 6 and 7 because nodes were being removed, but not unbound (to be fair, I didn’t write the code, I was just fixing it ). I solved the issue by delegating an event listener at a higher point in the DOM structure than the nodes that were being removed from (http://api.jquery.com/delegate/).

    Not the one solution for all event-related problems, but still very handy to know about nonetheless.

    Commented on June 17, 2011 at 11:12 am
  • Rodney Rehm says:
    If you’re not working on some elaborate framework (but a collection of simple jQuery plugins or some such) you may prefer an event based cleanup such as: https://gist.github.com/1051257

    $(‘.this-node’).removeAfter(‘.this-node-has-been-removed’);

    Commented on June 28, 2011 at 11:12 am
  • Great great article on a hot topic.. Thank you!

    IMHO you could also mention event bubbling and reusing/updating existing UI components with new data.

    Many developers create event handlers for every element they load or do new Component(data) instead of component.update(data) in every ajax call increasing app’s memory footprint with no reason (even if gc works well and leakage is not a problem).

    Commented on July 16, 2011 at 11:13 am
  • Thank you for this great article!

    Commented on August 11, 2011 at 11:13 am

Leave a comment

Your email address will not be published. Required fields are marked *

Connect with Facebook

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Sign up to receive email communications regarding events, webinars, and product news.

What is Socialcast?

Socialcast by VMware (NYSE: VMW) is a social network for business uniting people, information, and applications with its real-time enterprise activity stream engine. Behind the firewall or in the cloud, Socialcast enables instant collaboration in a secure environment. Socialcast is headquartered in San Francisco, California. www.socialcast.com