I am working on a large enterprise application with a LOT of JavaScript. Enough that I can't possibly go through and fix all the small circular references that have been created over its past 5 years of development. While researching solutions I came across this small jQuery hack/patch:
http://kossovsky.net/index.php/2009/07/ie-memory-leak-jquery-garbage-collector/
and decided to try it. Amazingly, it works! sIEVE shows no leaks in the places I had previously identified them and the iexplore task is maintaining a more manageable memory footprint.
My question is, why does this work? jQuery.remove calls .removeChild, which should get rid of the element, but apparently does not. The patch instead appends the target element onto a new garbage collector div, which it then clears. Why does the patch method of removal completely free up the memory but jQuery's remove function does not? I'm hoping to understand why this works in order to possibly improve the solution before I check it in to the larger application.
This is the .remove
method in the current jQuery release (1.6.2). Notice that it calls .cleanData
:
// keepData is for internal use only--do not document
remove: function( selector, keepData ) {
for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
if ( !keepData && elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
jQuery.cleanData( [ elem ] );
}
if ( elem.parentNode ) {
elem.parentNode.removeChild( elem );
}
}
}
return this;
},
And the .cleanData
method which it calls, which mentions a ticket number and allegedly prevents that horrible leak (according to one of the comments):
cleanData: function( elems ) {
var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
continue;
}
id = elem[ jQuery.expando ];
if ( id ) {
data = cache[ id ] && cache[ id ][ internalKey ];
if ( data && data.events ) {
for ( var type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
// This is a shortcut to avoid jQuery.event.remove's overhead
} else {
jQuery.removeEvent( elem, type, data.handle );
}
}
// Null the DOM reference to avoid IE6/7/8 leak (#7054)
if ( data.handle ) {
data.handle.elem = null;
}
}
if ( deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ];
}
}
}
And here is the ticket mentioned in the comment. Apparently it was fixed eight months ago:
http://bugs.jquery.com/ticket/7054#comment:10
According to Dave Methvin the solution seems to be to Ensure that the DOM element ref in an event handler is removed by cleanData to avoid an IE6/7/8 memory leak.
In other words, set references to DOM elements within event handlers to null
otherwise some awesome browsers, without mentioning any names cough IE cough will leak memory.
discardElement
(from your link) inserts the element into a container, and then empties the container, thereby nullifying any references to that element.
With that in mind, I would suggest upgrading jQuery. The article you point to is from 2009, and two years is roughly equivalent to four-hundred-zillion man hours of jQuery development time.
Finally, here is some interesting (and ridiculously long) reading on leak patterns in Internet Explorer:
I'm going to theorise that it's similar to .net garbage collection, in that it relies on Pinned objects in the heap.
IE is treating the parent of a removed object like a pin and not clearing the removed object down properly.
The act of moving the deleted item to this generated gc container is basically removing the pin because IE knows that nothing is relying on that container.
That's my gut feeling anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With