I am working on a web application that is designed to display a bunch of data that is updated periodically with AJAX. The general usage scenario would be that a user would leave it open all day and take a glance at it now and then.
I am encountering a problem where the browsers memory footprint is growing slowly over time. This is happening in both Firefox and IE 7 (Although not in Chrome). After a few hours, it can cause IE7 to have a footprint of ~200MB and FF3 to have a footprint of ~400MB.
After a lot of testing, I have found that the memory leak only occurs if the AJAX calls are being responded to. If the server doesn't respond to anything, I can leave the page open for hours and the footprint won't grow.
I am using prototype for my AJAX calls. So, I'm guessing there is an issue with the onSuccess callback creating these memory leaks.
Does anyone have any tips on preventing memory leaks with prototype / AJAX? Or any methods on how to troubleshoot this problem?
EDIT: found out the issue lies in a js graphing library I am using. Can be seen here.
The biggest thing you can watch out for is events, and how you assign them.
For instance, take this scenario (since you haven't provided one):
<div id="ajaxResponseTarget">
...
</div>
<script type="text/javascript">
$(someButton).observe('click', function() {
new Ajax.Updater($('ajaxResponseTarget'), someUrl, {
onSuccess: function() {
$$('#ajaxResponseTarget .someButtonClass').invoke('observe', 'click', function() {
...
});
}
});
});
</script>
This will create a memory leak, because when #ajaxResponseTarget
is updated (internally, Prototype will use innerHTML
) elements with click
events will be removed from the document without their events being removed. The second time you click someButton
, you will then have twice as many event handlers, and garbage collection can't remove the first set.
A way to avoid this is to use event delegation:
<div id="ajaxResponseTarget">
...
</div>
<script type="text/javascript">
$('ajaxResponseTarget').observe('click', function(e) {
if(e.element().match('.someButtonClass')) {
...
}
});
$(someButton).observe('click', function() {
new Ajax.Updater($('ajaxResponseTarget'), someUrl);
});
</script>
Because of the way DOM events work, the "click" on .someButtonClass
will fire also on #ajaxResponseTarget
, and Prototype makes it dead simple to determine what element was the target of the event. No events are assigned to elements within #ajaxResponseTarget
, so there is no way for replacing its contents to orphan events from targets within.
I may be wrong but it sounds like you are creating closures around the response object. Each response object will be different which results in an increased memory footprint.