Plug those Javascript memory leaking holes

Recently I built a Javascript app that uses Gmap API, Ajax and JQuery to display the location information on a web page. It retrieves information from server using Ajax and the returned json object, to display location information for certain things like business, hotel and schools on a Google map. The project is fun to work on and I learned a good lessen on Javascript memory leak as well.

As I worked on testing and development the code in my browser, I realized the browser was getting less and less responsive. So I opened up the task manager to find out what’s going on and found out that the browser’s memory usage has been continuing growing every time I refreshed the page, and the usage wouldn’t come back. And at one point, Firefox is hogging over 600M or memory on my system. And the bad behavior is consistent across Internet Explorer and Firefox. This means if a user use the page for some extensive time he’ll have to shutdown the browser to re-claim the system memory.

Not a very good user experience. Being a amateur Javascript developer I don’t have a lot of insight on how the memory is managed in Javascript. As a matter of fact, it has been largely ignored in my patty little scripts before. This time I have to take it seriously. So I hit up the web to see what could have caused the Javascript memory leak.

What could cause memory leaks

When we talk about Javascript memory leak, we often refer to the behavior that memory is not properly de-allocated when a web page is closed or refreshed. Web browsers have the build-in garbage collectors to collect and release the memory when it’s not used anymore. Usually when a page is done, the browser will remove everything created by Javascript, unless, however there are connections between Javascript objects (variables and functions) and DOM elements.

Another situation should probably be better addressed as memory management. A good Ajax app lets use to access different information while staying on the same page and in this case, the app can also eat up memory and become sluggish over the time. Although most of the time we might not notice this but a poorly written app can ramp up memory usage quickly and cause some bad user experience.

In theory, what should be avoided

The two biggest culprits that will cause memory leak are “Circular reference” and “closure”. Other people have written good articles about them. This article illustrates the problems in a relatively simple explanation. “Circular reference” is basically what I would call it “spaghetti code” in Javascript. The modern Object Oriented languages have helped reduce this kind of code, but in Javascript, it can easily happen. A simple example is to have a DOM element refers to a Javascript function (using onclick event call for example), and the Javascript function refers to the DOM element. If you used inner function in Javascript, you know what closure is. It is convenient in terms of inheriting the variables but it is also an easy way to hide the circular reference.

These two things probably are the source of most of the memory leaking problems. However, theories aside, I found it hard to spot the case in real code. Most of the time the code don’t really resemble the evil demos. The best way to avoid a problem is to stay away from it. So I come up with some good practices here that I believe which will be helpful to avoid problems, or at lease, minimize the risk to the lowest level.

Watch the variables

There are two type of variables in terms of the scope. One is declared outside a function, with or without the var keyword; the other is declared inside a function, WITH the var keyword. The later obviously is supposed to live a shorter life and only be used in the function scope. Although the first type is often referred as “global variable”, it is not necessarily the same as the global variable in other languages because it’s only supposed to live in the lifetime of a page.

The key is “supposed to”. Since Javascript runs in browsers, it becomes browser’s responsibility to release the memory allocated to variables when they are not in use. To judge whether a variable is “in use”, browser checks if there is any other reference to this object. If there is, browser will not de-allocate it. As more activities happening the browser will continue to take but not release any memory.

Because they don’t always do. Here is an scenario when “closure” is used in Javascript code. One common example for closure is using a function inside a function. But there is more. To understand more about closure, this is a great article.

But closure can easily cause memory leak if used like this:

function localDocElem() {

var elem = document.getElementById(‘ClickElem’);

elem.bind(‘click’, function() { elem.innerHTML = ‘<span class=”highlight”>’ + elem.innerHTML + ‘</span’; });
}

This function simply attach a click event to a DOM element so whenever it is clicked the content gets “highlighted”. However, the function attached to the DOM object “elem” also uses the DOM object, hence, a circular reference is created between the DOM object and function object (in Javascript, even a function is maintained as an object) so the browser won’t garbage collect the local variable elem even after the localDocElem function returns. In this particular example, since a DOM object is connected with Javascript function, it is possible that the browser does not garbage collect either even the user leaves the page and the only way to claim the memory back is to shutdown the browser completely.

Consider using global variable to hold large amount of data can reduce some of the risk above. Let’s you have a Javascript that uses Ajax to to pull data from the server constantly, you can use JQuery’s $.getJson method to archive this. The returned data is better to be held in a global variable than local.

Un-allocate and nullify often

In a map application, lots of objects like icons, markers, etc. are created dynamically. They take memory to store and they can take up large chunk of memory quickly. It is a better practice to “nullify” them whenever they are not in use, for example, when displaying a completely different set of markers in a map view. Usually we can just assign the null value to the variable to achieve this, however for a clean de-allocation you’ll need to make sure to remove anything that is attached to them. For example, events, callback functions.

Remove listeners

To make an app interactive to user activities we create event listeners. Take a Gmap app example, we add click listener to an icon so when it is clicked, more detailed information is show. In some other cases, you could add a click event listener on a div element so whenever it is clicked, the background color changes. Since listeners and the function they trigger are attached to an object, you can see this is another easy case to get into the circular reference loop. In my code, a lot of listeners are created on the fly for those Gmap “markers”.

What I found helpful was to promptly remove the listeners when the markers are refreshed (along with the code to nullify the marker variables).

Delete the dynamically generated DOM objects

Often we use Javascript to create DOM elements, like “populating” a div tag on the fly. Since some browsers manage the DOM objects and script objects differently, the circular reference scenario can be easily created like discussed above. When you are done with the content and move on to something else, it might be helpful to eliminate the DOM element completely. For example, if you have update the content of a Div tag, you can empty out its inner HTML like this:

document.getElementById(‘MyAjaxTarget’).innerHTML = ”

Create cleanup methods for different stages

GUnload is provided as default Gmap cleanup call when the page is unload. However it is probably not enough since it is only called when a use leaves or refreshes a page. What we need to make sure is that memory is always released promptly when not in use. So I created several “cleanup” methods just to do that. The cleanup methods can include everything talked above, and they are called whenever needed in the code flow.

In summary, while Javascript enables us to enrich the user experience it can also do just the opposite if not done correctly. Although the modern browsers are doing much better jobs today to manage the Javascript garbage collection, we still need to do our due diligence to develop the code that are not only fancy, but also “responsible” to our users.

Here is the link of my Google map app if you are interested.

If you have a good tip to help better manage memory usage in a Javascript, please share your comment!

This entry was posted in my 2 cents. Bookmark the permalink.