My favorites | Sign in
Google
                
Search
for
Updated Mar 05, 2008 by bruce+pe...@google.com
DomEventsAndMemoryLeaks  
An explanation of DOM events, memory leaks, and how GWT handles both.

DOM Events, Memory Leaks, and You

Joel Webber

You may ask yourself, "Why do I have to use bitfields to sink DOM events?", and you may ask yourself, "Why can I not add event listeners directly to elements?". If you find yourself asking these questions, it's probably time to dig into the murky depths of DOM events and memory leaks.

If you're creating a Widget from scratch (using DOM elements directly, as opposed to simply creating a Composite), the setup for event handling is a bit odd. Generally, it looks something like this:

class MyWidget extends Widget {
  public MyWidget() {
    setElement(DOM.createDiv());
    sinkEvents(Event.ONCLICK);
  }

  public void onBrowserEvent(Event evt) {
    switch (DOM.eventGetType(evt)) {
      case Event.ONCLICK:
        // Do something insightful.
        break;
    }
  }
}

This may seem a bit obtuse, but there's a good reason for it. To understand this, you may first need to brush up on browser memory leaks. There are some good resources on the web:

The upshot of all this is that in some browsers, any reference cycle that involves a Javascript object and a DOM element (or other native object) has a nasty tendency to never get garbage-collected. The reason this is so insidious is that this is an extremely common pattern to create in Javascript UI libraries. Imagine the following (raw Javascript) example:

function makeWidget() {
  var widget = {};
  widget.someVariable = "foo";
  widget.elem = document.createElement ('div');
  widget.elem.onclick = function() {
    alert(widget.someVariable);
  };
}

Now, I'm not suggesting that you'd really build a Javascript library quite this way, but it serves to illustrate the point. The reference cycle created here is:

widget -> elem(native) -> closure -> widget

There are many different ways to run into the same problem, but they all tend to form a cycle that looks something like this. This cycle will never get broken unless you do it manually (often by clearing the onclick handler).

There are a number of ways developers try to deal with this issue. One of the more common I've seen is to walk the DOM when window.onunload is fired, clearing out all event listeners. This is problematic for two reasons:

  • It doesn't clear events on elements that are no longer in the DOM.
  • It doesn't deal with long-running applications, which are becoming more and more common.

GWT's Solution

When designing GWT, we decided that leaks were simply unacceptable. You wouldn't tolerate egregious memory leaks in a desktop application, and a browser app should be no different. This raises some interesting problems, though. In order to avoid ever creating leaks, any Widget that might need to be get garbage collected must not be involved in a reference cycle with a native element. There's no way to find out "when a widget would have been collected had it not been involved in a reference cycle". So in GWT terms, a Widget must not be involved in a cycle when it is detached from the DOM.

How do we enforce this? Each Widget has a single "root" element. Whenever the Widget becomes attached, we create exactly one "back reference" from the element to the Widget ( i.e. elem.__listener = widget, performed in DOM.setEventListener()). This is set whenever the Widget is attached, and cleared whenever it is detached.

Which brings is back to that odd bitfield used in sinkEvents(). If you look at the implementation of DOM.sinkEvents(), you'll see that it does something like this:

  elem.onclick = (bits & 0x00001) ? $wnd.__dispatchEvent : null;

Each element's events point back to a central dispatch function, which looks for the target element's __listener expando, in order to call onBrowserEvent(). The beauty of this is that it allows us to set and clear a single expando to clean up any potential event leaks.

What this means in practice is that, as long as you don't set up any reference cycles on your own using JSNI, you can't write an app in GWT that will leak. We test carefully with every release to make sure we haven't done anything in the low-level code to introduce new leaks as well.

The downside, of course, is that you can't hook event listeners directly to elements that are children of a Widget's element. Rather, you have to receive the event on the Widget itself, and figure out which child element it came from.

But that's better than leaking gobs of memory on your users' machines, right?


Comment by paulsschwarz, Jul 19, 2007

Thanks for the excellent explanation and thanks for keeping our apps memory leakless!

Comment by dfd...@users.sourceforge.net, Aug 19, 2007

Thanks, very useful document!

You said that you test carefully with every release to make sure we haven't done anything in the low-level code to introduce new leaks as well.

How do you know you have a new memory leak in IE?

Comment by gwt.team.jgw, Sep 20, 2007

I typically use Drip (http://code.google.com/p/iedrip/), which I wrote some time ago. It's not perfect, but catches most leaks in practice.

Comment by chirino, Oct 24, 2007

The one downside is that 2 gwt modules on the same page are going to conflict with each other since the last one will likely overwrite the $wnd.dispatchEvent.

Comment by ganesh.bansal, May 23, 2008

I am creating an application using GWT. It works fine in Mozilla while in IE (6 & 7 both), it slows down as I run my application. While I checked in "Windows Task Manager", I found that memory usage is increasingly continuously in IE while in Mozilla it remains constant. First I assumed that reason for this is memory leak in IE. But when I read this article, acc. to this, memory leak is properly handled in GWT and it can't be a problem (As I am not using JSNI anywhere in my application). But than why my application gets slow down in IE and also memory usage is increasing regularly (while it runs fine in mozilla)? PLEASE HELP ME as it has become a blocker issue for deployment of my application.

Comment by CucerV, May 28, 2008

We have the same problem ganesh has. The memory is increasing continuously and is not freed even after refresh of the page. Using only GWT and Incubator Widgets.

Comment by david.nouls, May 28, 2008

Forms and IFrames can cause leaks in IE6/7

Comment by ganesh.bansal, May 29, 2008

But I m using neither Forms not IFrames, still lot of memory leaks in IE6/7.

Comment by ajr@google.com, May 30, 2008

@Ganesh: Can you post a link to some code, and then we can take a look at it? Also, what version of GWT are you using?

Thanks!

Comment by ganesh.bansal, May 31, 2008

I am creating a very big application and it's not feasible to post the code. Still I will try to make some sample code on weekend which causes memory leaks and post it asap. In the mean time, if u can suggest some points which should be taken care for avoiding memory leaks while creating GWT application, than it can be very helpful for me.

Thanks!

Comment by k...@calient.net, Oct 24, 2008

Hello... has the issues brought up by ganesh.bansal been resolved - memory leaks in IE6/7 with GWT ???

Comment by cyril.la...@gmail.com, Jan 05, 2009

Hi, Same problem here, big memory leaks with GWT on IE6 but NOT on firefox/opera etc.

I saw memory increasing on IE6 when closing a tab and when I opened it again and again... each time I reopen a tab (a tab with many widgets inside) I see the memory increasing only in IE6 !!!

Any help would be great !!!

Cyril Lakech http://cyrillakech.blogspot.com

Comment by ecc@google.com, Jan 05, 2009

Cyril, Can you enter an issue report with some sample code here(http://code.google.com/p/google-web-toolkit/issues/entry)? Feel free to e-mail me the issue number directly after you are done, as any memory leak you can create with just a tab panel + widgets without iframes, etc. is one we'd definitely want to track down!

Comment by Alexey.Tsiunchik, Jan 14, 2009

We have the same issues as described by ganesh.bansal abd cyril.lakech with GWT 1.5.2. It seems that memory in IE never released properly (I mean released less memory then was allocated during widgets creation). But if page is refreshed (just pressing refresh button) IE release allocated memory. Can you provide any suggestions or guides to program with GWT and avoid memory leaks?.

Comment by david.nouls, Jan 16, 2009

IE's garbage collector is extremely lazy. Are you certain that it's maybe not just IE not doing any GC? Try to run your application with drip as asked before and that should give you a better idea.

Is there a possibility to enforce the GC to happen in IE ? Just to be able to see if it is really a memory leak or the laziness of GC invocations ?

Comment by sachindatta, Feb 16, 2009

Just use the applicationCreator to create a starter application and test it using JS Leak Detector in IE7. It leaks memory! You can use JS Leak Detector against Windows Live Hotmail and it will show no leak!

Comment by jgw+personal@google.com, Feb 16, 2009

@sachindatta: Thanks for pointing out the JS Leak Detector. It's much more up-to-date than my crufty old Drip app for finding leaks. But to determine whether an app actually leaks, read the documentation carefully -- in its default mode (IE7), it identifies patterns that might leak, by looking for circular reference patterns. If you switch to "Actual leaks" mode, you'll find that none of our samples leak. This happens because GWT generates circular reference patterns for every widget, but guarantees that they get cleaned up whenever a widget is detached, and forcibly detaches all widgets on window unload.

Comment by jgw+personal@google.com, Feb 16, 2009

@Cyril, Alexey: Can you check your app against GWT 1.5 if you haven't already? There was a bug in GWT 1.4 ( issue 2014 ) that caused leaks under certain circumstances that we weren't adequately testing, but are now.

Comment by danielmgerson, May 07, 2009

I want to know about wrapping elements in iframes with widgets and if this event model supports this. The full question is here: http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/b53fc025f0dd3653

Comment by nkreiff, Oct 06, 2009

excellent explanation! thanks!


Sign in to add a comment