|
DomEventsAndMemoryLeaks
An explanation of DOM events, memory leaks, and how GWT handles both.
DOM Events, Memory Leaks, and YouJoel 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:
GWT's SolutionWhen 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? |
Sign in to add a comment
Thanks for the excellent explanation and thanks for keeping our apps memory leakless!
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?
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.
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.
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.
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.
Forms and IFrames can cause leaks in IE6/7
But I m using neither Forms not IFrames, still lot of memory leaks in IE6/7.
@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!
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!
Hello... has the issues brought up by ganesh.bansal been resolved - memory leaks in IE6/7 with GWT ???
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
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!
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?.
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 ?
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!
@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.
@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.
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
excellent explanation! thanks!