- Orphaned JS/DOM objects.
- Failed garbage collection in Internet Explorer due to circular references between JS objects.
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.
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:
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:
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.
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.
- Highlight: stack traces for memory leaks.
- 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.
*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.
These are a sampling of the better resources for understanding memory leaks. I recommend starting with the IBM article for the clearest examples.