Skip to main content

Bug Tracker

Side navigation

#8171 closed bug (invalid)

Opened February 03, 2011 08:47PM UTC

Closed February 04, 2011 01:48AM UTC

Last modified March 13, 2012 05:22PM UTC

Memory leak in IE8 with .remove()/jQuery.cleanData()

Reported by: smudgeface Owned by:
Priority: low Milestone: 1.next
Component: manipulation Version: 1.5
Keywords: Cc:
Blocked by: Blocking:
Description

I believe I may have found a memory leak in the latest release of jQuery.

The following code leaks:

var  listen = function(){};
var testLeak = function(){
    for(var i = 0; i<100; i++){
        var item = {};
        item.elem = $(document.createElement('div'));
        item.addListener = function(name,listener){
            var self = this;
            var wrappedListener = function(){
                return listener.apply(self,arguments);
            }
            this.elem.bind(name, wrappedListener);
            wrappedListener = null;
        }
        item.addListener('eventName',listen );
        item.elem.unbind();
        item.elem.remove();  //with this un-commented, the loop leaks
        // item.elem = null; //with this also un-commented, the leak dissapears
    }
};
    
$(document).ready(function(){
    setInterval(testLeak, 100);
}

also, I have created a jsfiddle link which demonstrates the issue:

http://jsfiddle.net/rJ8x5/5/

It is important to note that if I do not call .remove(), this does not leak, and if I do call .remove() but set the .elem reference to null, it also does not leak. It is as though jQuery is holding a reference to item when I call .remove() which, in-turn, holds a reference to .elem. Any thoughts? As you can see, I have set the wrappedListener to null in an attempt to prevent any inadvertent closures, but this does not help.

Attachments (0)
Change History (9)

Changed February 03, 2011 09:18PM UTC by jitter comment:1

component: unfiledmanipulation
priority: undecidedlow
resolution: → invalid
status: newclosed

Thanks for taking the time to contribute to the jQuery project by writing a bug report and providing a test case!

After checking your report I came to the conclusion that this isn't a bug in jQuery but rather a wrong assumption about what the .remove() method does.

The .remove() documentation reads:

Description: Remove the set of matched elements from the DOM.

In your case item.elem is a jQuery object which holds a detached div. Thus .remove() can't remove it from the DOM as the div isn't in the DOM.

So just leave away the item.elem.remove() line and use item.elem = null.

Changed February 03, 2011 09:26PM UTC by smudgeface comment:2

_comment0: Respectfully, I disagree with this conclusion. \ \ if you read further in the documentation you will see \ \ > In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed. \ \ this is because before the element is removed, two calls to jQuery.cleanData is made. \ \ the actual remove functionality is performed as \ \ {{{ \ if ( elem.parentNode ) { \ elem.parentNode.removeChild( elem ); \ } \ }}} \ \ this will valid whether the Element is in the DOM or not. The real problem here is in jQuery.cleanData, which is referred to in this bug title1296768427974638

Respectfully, I disagree with this conclusion.

if you read further in the documentation you will see

In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.

this is because before the element is removed, two calls to jQuery.cleanData are made.

jQuery.cleanData( elem.getElementsByTagName("*") );
jQuery.cleanData( [ elem ] );

the actual remove functionality is performed as

if ( elem.parentNode ) {
    elem.parentNode.removeChild( elem );
}

this will valid whether the Element is in the DOM or not. The real problem here is in jQuery.cleanData, which is referred to in this bug title

Changed February 03, 2011 09:38PM UTC by smudgeface comment:3

BTW, the same behavior is exhibited even if the Element is in the DOM

var  listen = function(){};
var testLeak = function(){
    for(var i = 0; i<100; i++){
		item.elem = $(document.createElement('div'));
		$(document.body).append(item.elem);
		item.addListener = function(name,listener){
			var self = this;
			var wrappedListener = function(){
				return listener.apply(self,arguments);
			}
			this.elem.bind(name, wrappedListener);
			wrappedListener = null;
		}
		item.addListener('eventName',listen);
		item.elem.unbind();
		item.elem.remove();  //with this un-commented, the loop leaks
		// // item.elem = null; //with this also un-commented, the leak dissapears
    }
};
    
$(document).ready(function(){
    setInterval(testLeak, 100);
}

in this case I am using .remove() exactly as the documentation specifies

Changed February 03, 2011 09:47PM UTC by anonymous comment:4

Finally! I'm experiencing a similar problem. I wrap my own callback like smudgeface above, and upon the remove() method I'm seeing a leak in IE8.

Changed February 03, 2011 11:35PM UTC by jitter comment:5

resolution: invalid
status: closedreopened

Reopening as I missed some points the reporter made.


Replying to [comment:2 smudgeface]:

if you read further in the documentation you will see > In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.

I'm (as you probably can imagine) very well aware of that and how this is done internally. And as you can verify with this test case by commenting/uncommenting the unbind/remove lines they do exactly that.


Now it would be nice if you could provide more information. Does this also happen with other browsers? Does this only happen with jQuery 1.5 (what about earlier versions 1.4.4, 1.4.2, ...)

Changed February 04, 2011 12:02AM UTC by smudgeface comment:6

_comment0: Sorry, I should know better than to file partial bug reports. \ \ Methodology: \ -load up [http://jsfiddle.net/rJ8x5/8/] \ -watch memory usage using Sysinternals Process Explorer or Windows Task Manager \ -refresh the page \ \ Expected Outcome: \ -Stable memory usage \ \ Visible Outcome: \ -Memory continues to increase as long as page is open \ -Refreshing the page '''does not''' free up all consumed memory \ -this indicates both a pseudo-leak as well as an IE cross-page leak \ \ Reproducibility: \ -This is highly reproducible in IE8 as well as IE8 (compat) and IE7 \ -I am unable to test in IE6 \ -Not reproducible in Webkit/Mozilla \ \ jQuery History: \ -Using jsfiddle I was able to confirm that the leak exists on all major releases of jQuery, back to 1.3.2 (see note) \ -I updated my original link to include actual DOM insertion [http://jsfiddle.net/rJ8x5/8/] \ \ \ note: The fact that this issue exists using old versions of jQuery is alarming to me. Either there is a fundamental problem with the way I am creating the inner closure, or there is a fundamental problem with jQuery. I am unable to see anything wrong with my code, however... \ 1296777771982419
_comment1: Sorry, I should know better than to file partial bug reports. \ \ Methodology: \ -load up [http://jsfiddle.net/rJ8x5/8/] \ \ -watch memory usage using Sysinternals Process Explorer or Windows Task Manager \ \ -refresh the page \ \ Expected Outcome: \ -Stable memory usage \ \ Visible Outcome: \ \ -Memory continues to increase as long as page is open \ \ -Refreshing the page '''does not''' free up all consumed memory \ \ -this indicates both a pseudo-leak as well as an IE cross-page leak \ \ Reproducibility: \ -This is highly reproducible in IE8 as well as IE8 (compat) and IE7 \ -I am unable to test in IE6 \ -Not reproducible in Webkit/Mozilla \ \ jQuery History: \ \ -Using jsfiddle I was able to confirm that the leak exists on all major releases of jQuery, back to 1.3.2 (see note) \ \ -I updated my original link to include actual DOM insertion [http://jsfiddle.net/rJ8x5/8/] \ \ \ note: The fact that this issue exists using old versions of jQuery is alarming to me. Either there is a fundamental problem with the way I am creating the inner closure, or there is a fundamental problem with jQuery. I am unable to see anything wrong with my code, however... \ 1296777882184616

Sorry, I should know better than to file partial bug reports.

Methodology:

-load up http://jsfiddle.net/rJ8x5/8/

-watch memory usage using Sysinternals Process Explorer or Windows Task Manager

-refresh the page

Expected Outcome:

-Stable memory usage

Visible Outcome:

-Memory continues to increase as long as page is open

-Refreshing the page does not free up all consumed memory

-this indicates both a pseudo-leak as well as an IE cross-page leak

Reproducibility:

-This is highly reproducible in IE8 as well as IE8 (compat) and IE7

-I am unable to test in IE6

-Not reproducible in Webkit/Mozilla

jQuery History:

-Using jsfiddle I was able to confirm that the leak exists on all major releases of jQuery, back to 1.3.2 (see note)

-I updated my original link to include actual DOM insertion http://jsfiddle.net/rJ8x5/8/

''note: The fact that this issue exists using old versions of jQuery is alarming to me. Either there is a fundamental problem with the way I am creating the inner closure, or there is a fundamental problem with jQuery. I am unable to see anything wrong with my code, however...''

Changed February 04, 2011 01:03AM UTC by jitter comment:7

Thanks, I really appreciate your willingness to help in resolving this problem and the quick feedback when asked for more information. This saves us big amounts of time when debugging such issues.

After poking around a bit, can you try if the following code changes fixes the leak for you?

[...]
item.addListener = function(name,listener){
    var self = this;
    var wrappedListener = function(){
        return listener.apply(self,arguments);
    }
    this.elem.bind(name, wrappedListener);
    wrappedListener = null;
    self = null; // <-- this is the change
}
[...]

Your addListener code looks similar to this IE closure leak pattern

Changed February 04, 2011 01:43AM UTC by smudgeface comment:8

After playing around even further I think we can put some closure to this (pun intended).

My recent jsfiddle has completely removed jquery and gotten to the core of this problem. The memory leak is still exhibited and is likely a result of the wrappedListener holding scope to self/this. and is corrected by setting item.elem = null.

http://jsfiddle.net/rJ8x5/34/

My confusion was caused by the fact that in my previous example (http://jsfiddle.net/rJ8x5/8/) the memory leak did not occur even if I did not set item.elem = null, but only if I didn't call .remove(). Odd behavior...

Needless to say, I think this issue can be closed. Comments?

Changed February 04, 2011 01:48AM UTC by jitter comment:9

resolution: → invalid
status: reopenedclosed

Cool, closing (as invalid because not jQuery related).

Good to hear you were able to pin-point the issue and also could exclude jQuery from having something to do with this issue.

You might also want to look at Understanding and Solving Internet Explorer Leak Patterns for further info on memory leak problems with IE.