More on creating memory leak test scripts

The plot thickens. I now have succesfully created a simple function that causes memory leaks. The problem is, I don't understand why.

Update: Incorrect experiments. This entry is closed.

No leaks

The following script does not cause memory leaks (test it). That's a relief, because I often use circular references between two HTML objects, for instance a header and a div that should open when the user clicks on the header. Using such references makes for far simpler scripts, since I don't have to find the related element every time I need it.

function init()
{
	createLinks();
	var x = document.getElementsByTagName('a');
	for (var i=0;i<x.length;i+=2)
	{
		if (!x[i].nextSibling) return;
		x[i].relatedElement = x[i].nextSibling;
		x[i].nextSibling.relatedElement = x[i];
	}
}

Leaks

However, if I change the script slightly, it does leak memory, about 2,800 K per reload (test it).

function init()
{
	createLinks();
	var x = document.getElementsByTagName('a');
	for (var i=0;i<x.length;i+=2)
	{
		treatOneLink(x[i]);
	}
}

function treatOneLink(obj)
{
	if (!obj.nextSibling) return;
	obj.relatedElement = obj.nextSibling;
	obj.nextSibling.relatedElement = obj;
}

Why this difference?

Update

Update after comment 8: the circular reference is definitely needed to trigger the memory leak, though an as yet unknown component is also needed.

Evidence: the following script does not leak (test it), and the only difference with the leaking script is that I don't create circular references here:

function init()
{
	createLinks();
	var x = document.getElementsByTagName('a');
	for (var i=0;i<x.length;i+=2)
	{
		treatOneLink(x[i]);
	}
}

function treatOneLink(obj)
{
	if (!obj.nextSibling) return;
	obj.relatedElement = obj.nextSibling;
}

This is the blog of Peter-Paul Koch, web developer, consultant, and trainer. You can also follow him on Twitter or Mastodon.
Atom RSS

If you like this blog, why not donate a little bit of money to help me pay my bills?

Categories:

Comments

Comments are closed.

1 Posted by Tino Zijdel on 20 October 2005 | Permalink

Your first example does leak, but not so much. It looks like in the first example IE only leaks the last item of the x-collection, in the second example every obj reference leaks. Scope seems to be an important factor.

2 Posted by ppk on 20 October 2005 | Permalink

But WHY this difference? Please explain clearly.

3 Posted by Gerhard Hoogterp on 20 October 2005 | Permalink

No final answer here, but.. the moment you do

x[i].relatedElement = x[i].nextSibling;

the original pointer to the data used by x[i].relatedElement is gone. Never to be seen again.
I don't know about javascript garbage collection, but in other, simpler, languages it would definitly be a problem.

4 Posted by ppk on 20 October 2005 | Permalink

There is no original pointer. The property relatedElement is created by this assignment.

5 Posted by Gerhard Hoogterp on 20 October 2005 | Permalink

Does it also leak if you don't pass the object as parameter but use it globaly? ie.

function treatOneLink()
{
if (!x[i].nextSibling) return;
x[i].relatedElement = x[i].nextSibling;
x[i].nextSibling.relatedElement = x[i];
}

6 Posted by ppk on 20 October 2005 | Permalink

Good question. Yes, it does. See http://www.quirksmode.org/js/memoryleaks/leaktest7.html for the test page.

7 Posted by Gerhard Hoogterp on 20 October 2005 | Permalink

Of course in that case the next question is: does it leak the same amount (so probably for the same reason) or a different amount (more than one problem).
I can't check it myself at the moment.. sorry.

I see that you now pass the iterator instead of a whole object. If the problem is in the parameter passing part (creating a copy of the passed parameter) I would expect the leak to be different.

If the leak is exactly the same I would think of the code needed for creating the function call.

Either case would be deep in the the scripting code and I doubt there's anything one can do on script level besides a work around (like simply don't do it like this..)

8 Posted by Will Rickards on 20 October 2005 | Permalink

I think it is a scope issue. When everything is contained within a function, the x[i] objects get created and destroyed in the same scope. When you pass a reference to the x[i] object to the function or use a global to track the x[i] objects, the objects are moving between scopes and aren't getting destroyed properly. Further testing without creating the circular reference is probably required. In the function that now creates the circular reference, maybe you should just set a function level variable to the object reference and see if it leaks. Then try setting window.x to the variable and see if it leaks.

9 Posted by ppk on 20 October 2005 | Permalink

Interesting idea, Will, but unfortunately it doesn't work that way. See the new test script I added to the main entry.

Gerhard: Yes, the leakage is roughly the same in both scripts.

10 Posted by Tino Zijdel on 20 October 2005 | Permalink

Maybe you should replace 'return' with 'continue' in your first testcase because it doesn't go further than the fourth anchor in your page ;)
It's purely the circular reference that is responsible for the leak, nothing more...

11 Posted by Peter Siewert on 20 October 2005 | Permalink

Tino has it. The first one leaks memory, just several orders of magnitude less, as it creates 4 circular references, not ~10000 like the second one. Your quirksmode.js file automaticly adds the table of contents with "A" elements to the top of the page. The first script will return from the "init" function when it reaches the end of the TOC, while the second will return from the "treatOneLink" function, but continue to itterate through the array of "A" elements in the "init" function.

12 Posted by Tino Zijdel on 20 October 2005 | Permalink

Note that referencing to an element that is a childNode of the object you attach it to is also sufficient to create a memory-leak (as shown in my testcase at http://therealcrisp.xs4all.nl/upload/leaktest2.html ) - so not only circular references are responsible.

13 Posted by Tino Zijdel on 20 October 2005 | Permalink

Besides circular references also referencing to an element that is a *child* of the element in which you store the reference causes memory leaks (as shown in my testcase at http://therealcrisp.xs4all.nl/upload/leaktest2.html ).

14 Posted by ppk on 20 October 2005 | Permalink

Right. Not disabling my sitewide script was obviously an error.

I'm going to close this entry now and write a new one.