Memory Leakage in Internet Explorer - revisited

In this article, we will review JavaScript memory leakage patterns from a slightly different perspective and support it with diagrams and memory utilization graphs.

Introduction

If you are developing client-side re-usable scripting objects, sooner or later you will find yourself spotting out memory leaks. Chances are that your browser will suck memory like a sponge and you will hardly be able to find a reason why your lovely DHTML navigation's responsiveness decreases severely after visiting a couple of pages within your site.

In this article, we will review those patterns from a slightly different perspective and support it with diagrams and memory utilization graphs. We will also introduce several subtler leak scenarios. Before we begin, I strongly recommend you to read that article if you have not already read.

Why does the memory leak?

The problem of memory leakage is not just limited to Internet Explorer. Almost any browser (including but not limited to Mozilla, Netscape and Opera) will leak memory if you provide adequate conditions (and it is not that hard to do so, as we will see shortly). But (in my humble opinion, ymmv etc.) Internet Explorer is the king of leakers.

Don't get me wrong. I do not belong to the crowd yelling "Hey IE has memory leaks, checkout this new tool [link-to-tool] and see for yourself". Let us discuss how crappy Internet Explorer is and cover up all the flaws in other browsers".

Each browser has its own strengths and weaknesses. For instance, Mozilla consumes too much of memory at initial boot, it is not good in string and array operations; Opera may crash if you write a ridiculously complex DHTML script which confuses its rendering engine.

If you have read my previous article, then you already know that I am a usability, accessibility and standards crack. I love Opera. But that does not mean I am pursuing a holy war against IE. What I want here is to follow up an analytical path and examine various leaking patterns that may not be quite obvious at first glance.

Although we will be focusing on the memory leaking situations in Internet Explorer, this discussion is equally applicable to other browsers.

The first assignment parentDiv=document.createElement(...); will create a div element and create a temporary scope for it where the scripting object resides. The second assignment parentDiv.bigString=... attaches a large object to parentDiv. When LeakMemory() method is called, a DOM element will be created within the scope of this function, a very large object will be attached to it as a member property and the DOM element will be de-allocated and removed from memory as soon as the function exits, since it is an object created within the local scope of the function.

When you run the example and click the button a few times, your memory graph will probably look like this:

Increasing the frequency

No visible leak huh? What if we do this a few hundred times instead of twenty, or a few thousand times? Will it be the same? The following code calls the assignment over and over again to accomplish this goal:

The ramp in the memory usage indicates leak in memory. The horizontal line (the last 20 seconds) at the end of the ramp is the memory after refreshing the page and loading another (about:blank) page. This shows that the leak is an actual leak and not a pseudo leak. The memory will not be reclaimed unless the browser window and other dependant windows if any are closed.

Assume you have a dozen pages that have similar leakage graph. After a few hours, you may want to restart your browser (or even your PC) because it just stops responding. The naughty browser is eating up all your resources. However, this is an extreme case because Windows will increase the virtual memory size as soon as your memory consumption reaches a certain level.

This is not a pretty scenario. Your client/boss will not be very happy, if they discover such a situation in the middle of a product showcase/training/demo.

A careful eye may have caught that there is no bigString in the second example. This means that the leak is merely because of the internal scripting object (i.e. the anonymous script onclick='foo()'). This script was not deallocated properly. This caused memory leak at each iteration. To prove our thesis let us run a slightly different test case:

If you don't know what a closure is, there are very good references on the web where you may find it. Closures are very useful patterns; you should learn them and keep them in your knowledge base.

And here is the graph that shows the memory leak. This is somewhat different from the former examples. The anonymous function assigned to parentDiv.onclick is a closure that closes over parentDiv, which creates a circular reference between the JS world and DOM and creates a well-known memory leakage issue:

To generate leak in the above scenario, we should click the button, refresh the page, click the button again, refresh the page and so on.

Clicking the button without a subsequent refresh will generate the leak only once. Because, at each click, the onclick event of parentDiv is reassigned and the circular reference over the former closure is broken. Hence at each page load there is only one closure that cannot be garbage collected due to circular reference. The rest is successfully cleaned up.

More leakage patterns

All of the patterns shown below are described in detail in Justing's article. I'm going through them just for the sake of completeness:

Here the global variable myGlobalObject refers to the DOM element LeakDiv; at the same time LeakDiv refers to the global object through its expandoProperty. The situation looks like this:

The above pattern will leak due to the circular reference created between a DOM node and a JS element.

Since the JScript garbage collector is a mark and sweep GC, you may think that it would handle circular references. And in fact it does. However this circular reference is between the DOM and JS worlds. DOM and JS have separate garbage collectors. Therefore they cannot clean up memory in situations like the above.

Another way to create a circular reference is to encapsulate the DOM element as a property of a global object:

Here is a diagram describing the closure which creates a circular reference between the DOM world and the JS world.

The above pattern will leak due to closure. Here the closure's global variable obj is referring to the DOM element. In the mean time, the DOM element holds a reference to the entire closure. This generates a circular reference between the DOM and the JS worlds. That is the cause of leakage.

This pattern will not leak because as soon as the function window.onload finishes execution, the JS object obj will be marked for garbage collection. So there won't be any reference to the DOM node on the JS side.

Since we observe memory leakage even in Exhibit 1, it is not surprising that this pattern leaks. Here is what happens: When we append childDiv to parentDiv, a temporary scope from childDiv to parentDiv is created which will leak a temporary script object. Note that document.createElement("<div onClick='foo()'>"); is a non-standard method of event attachment.

Simply using the "best practices" is not enough (as Justing has mentioned in his article as well). One should also adhere to standards as much as possible. If not, he may not have a single clue about what went wrong with the code that was working perfectly a few hours ago (which had just crashed unexpectedly).

We should keep in mind that, although it is the market leader, IE is not the only browser in the world. And writing IE-specific non-standard code is a bad practice of coding. The counter-argument is true as well. I mean, saying "Mozilla is the best browser so I write Mozila-specific code; I don't care what the heck happens to the rest" is an equally bad attitude. You should enlarge your spectrum as much as possible. As a corollary, you should write standards-compatible code to the highest extent, whenever possible.

Writing, "backwards compatible" code is "out" nowadays. The "in" is writing "forward compatible" (also known as standards compatible) code which will run now and in the future, in current and in future browsers, here and on the moon.

Conclusion

The purpose of this article was to show that not all leakage patterns are easy to find. You may hardly notice some of them, may be due to the small bookkeeping objects that become obvious only after several thousands of iterations. Knowing the internals of a process is an undeniable key to success. Be aware of the leak patterns. Instead of debugging your application in a brute-force manner, look at your code fragments and check whether there is any piece that matches a leakage pattern in your arsenal.

Writing defensive code and taking care of all possible leakage issues is not an over-optimization. To take it simpler, leak-proofness is not a feature the developer may choose to implement or not depending on his mood. It is a requirement for creating a stable, consistent and forward-compatible code. Every web developer should know about it. No excuses, sorry. There are several solutions proposed for leakage issues between a JS closure and a DOM object. Here are a few of them:

However, you should be aware of the fact that there are always unique cases where you may need to craft a solution for yourself. That's it for now. Although the article is not intended to be the "best practices in avoiding memory leakage", I hope it has pointed out some of the interesting issues.

Happy coding!

History

2005-11-12: Article created.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

Volkan is a java enterprise architect who left his full-time senior developer position to venture his ideas and dreams. He codes C# as a hobby, trying to combine the .Net concept with his Java and J2EE know-how. He also works as a freelance web application developer/designer.

He was born on May '79. He has graduated from one of the most reputable universities of his country (i.e. Bogazici University) in 2003 as a Communication Engineer. He also has earned his Master of Business Administration degree from a second university in 2006.

Comments and Discussions

I have noticed that there is a memory leak with asp.net 2.0 much the same as the leak javascript function you had demonstrated. The difference is that in .net the server renderes my controls and I have no control over that!!! Do you maybe have any tips/suggestions with regards to memory leak in .net rendering?

I'm having serious memory leak problem. I'm using Ajax to refresh the data in the Divs, each time the ajax call is being made, the memory is growing up and after some time I'm getting memory out of exception.

I'm just getting data from Ajax method(from .cs class) and assigning the returned string to the div in java script using innerhtml.

These leaks are real, don't get me wrong, but Task Manager is not a reliable way of finding leaks.

The issue is that Task Manager can't distinguish between memory belonging to a heap manager, memory belonging to a cache attached to the application, or memory waiting to be garbage collected. All it can say is the total committed size that the application has accessed.

These leaks are real, but you have to be exceptionally cautious with using Task Manager to draw any conclusion about anything related to memory.

Your article is the most recent info that I could find on Memory Leaks and appreciate you provide diagrams and more examples. Can you help me identify and fix a 'leak' I'm seeing at work?

We have a parent HTML page with an input textbox that gets updated / refreshed with data from an IFRAME (using customeserver side includes). The IFRAME refreshes every second to get the most current value for the textbox.

Then I went home and ran the same test case, waited for 10 minutes. No leak at all!

My conclusion is that the leak may be due to some sorta plugin/toolbar etc installed on internet explorer.

Oddly enough, I don't see anything in the code that may trigger a leak. I suspected that evals may cause some sort of leak (I don't know, may be by creating a temporary context of some sort -- I know it's not logical but to my knowledge that code piece should not have leaked at the beginning anyway - no closures, no cross references, no DOM magic, no nothing...)

Just an advice:
You may want to utilize AJAX instead of meta refreshes. This will sort out the leak issue for good.

Thanks for looking. I could not find any closures, cross references, or DOM issues either. That's what lead me to seek help. That is interesting that it does not occur on your home PC. Are the versions of IE you are running the same? Let's see, I'm using is using 6.0.2800.1106CO with SP1, Q833989, and Q823353 updates. Haven't been able to get my hands on 7.0 Beta to try that.

FYI, the leak also occurs in Netscape, but at a much lower rate. It does not occur at all in Firefox.

The only pattern that I've noticed is the size of the leak depends on the size of the IFRAME. The IFRAME originally had the JavaScript function updateUI() included in a common .js file.

When I removed that reference and included the function directly in the IFRAME page, the size of the leak was smaller. Taking it one step further, I placed the function in the Parent page and then called it from the IFRAME with parent.updateUI("W0", "W0", "text", WinhShift, "", "");.

Yeah, AJAX would be nice, but this is a custom embedded webserver for some Audio/Visual switchers that my company produces. We don't have any server side capabilities. All I get are some custom Server Side Includes to read the most current data values from the server.

Okay, finally figured this one out. I think this falls under Microsoft's Pseudo-Leak catagory, though I still think if Firefox can fix it, so can Microsoft.

The problem is with the META Refresh. I had to remember my fundementals of what the META Refresh was originally intended for, to redirect a user to a different site. You could always hit the back button and return to your original URL. Obviously, the IFRAME is being placed into History!

My solution is to drop the META Refresh and use window.location.reload(false) instead. Wa-la! No 'memory leak'!

I'll admit I only skimmed your article and will have to look at it in-depth later. However, who is going to perform some DHTML/JavaScript operation 50,000 times? Is this really a problem that a developer needs to be concerned with?

Especially you should be aware of leak patters that stem from closures, because they may occur in more real-life like scenarios.

For the 50000 iteration case; it is one object and 50000 iterations. Assume that you have a 30*40 grid (a table with td and th cells. and onload each cell creates a similar leak pattern (120 leaking objects), say you have more than one event-handler attached to it (onclick, onmouseover, onmouseout, onfocus, onblur :. 120*5->600 leaking objects).

And assume that the leaking objects are bound to larger objects which also prevent them being garbage collected. Then the result will be more dramatical.

You are partially correct though: We developers, sometimes, tend to over-optimize things.

Anyway, adhering to standards is a good thing. Being aware of peculiarities is another good thing.

As far as my experience is concerned (which I have pretty) seemingly minor things may grow exponentially if they combine; not linearly.

Anyway that's my personal judgement. Even if you disagree with me wrt the above lines; just keep the following in mind:

As a general rule of thumb, find a way to break circular references and 95% of the time you are done.

For the 50000 iteration case; it is one object and 50000 iterations. Assume that you have a 30*40 grid (a table with td and th cells. and onload each cell creates a similar leak pattern (120 leaking objects), say you have more than one event-handler attached to it (onclick, onmouseover, onmouseout, onfocus, onblur :. 120*5->600 leaking objects).

I definitely didn't even think that. Don't think I'm trying to pick apart your article or prove you wrong--far from it. I was really just asking how seriously this problem might affect someone like me.

No, I didn't thought about that. I tried to be explanatory but when re-reading I realized that it is somewhat defensive.

kryzchek wrote:

was really just asking how seriously this problem might affect someone like me.

To give an analogy think about Titanic: You will not notice the leaking cold water for a long time. And when you do notice, it would be too late to turn back.

Your actual need would depend on how large your app is and how heavily you rely on javascript in your app (I'm not talking about the fancy rollover buttons, slide in menus cursor tracker balls, etc etc by any means -- I mean how much your app depends on real-life client-side business objects)

It still depends but it --generally-- would not matter too much for a personal web site. But the issue may overkill for a web based CMS or ERP application.

Let me give an example from real life:

Say that you find yourself in the middle of a worldwide corporate project that had been developed for three years and at the end of third year you see that all of your js files are leaking (it may be several hundreds of files with half a million lines of js code, if it is a async ajax-like project with a fat client (architecturally fat I mean). ) )

Yes I had been in such a situation and believe me it was not a very nice experience.

- if you like caffeine, chinese food and pizzas and late night quake tournaments it's not a bad experience though )