Thanks Paul for sharing your experience with resolving memory leaks, I
think it worth a separate blog post.

Few questions:
1. what version of IE did you use during the testing ?
2. was the performance of the app improved after resolving the memory
leaks ?

Best regards,
Slava Lovkiy

On Nov 7, 7:10 pm, Paul McLachlan <[email protected]> wrote:
> I’d like to chronicle my experiences fixing a memory leak in our
> enterprise GWT application when running on Internet Explorer.
>
> A few facts getting started:
>
>   1. Using a click-recording tool, QA could get our application to
> leak hundreds of megabytes of memory in Internet Explorer.
>   2. No leak measured using Java memory analysis tools in hosted mode.
>   3. Non-trivial application - we have over 100K SLOC just for GWT
> (ie, not counting server side or any non .java files)
>
> Reproducibility was handled by QA, but the first problem was working
> out what was going on.  We didn't see these kinds of problems with
> Firefox & this strongly implies some kind of Internet Explorer
> circular reference strangeness between DOM elements and javascript.
>
> We spent some time playing with Drip and sIEve, but we were advised
> not to use these tools (misleading output) and didn't have wonderful
> success with them in any case.
>
> A bit more googling found a javascript memory leak analyzer from
> Microsoft 
> at:http://blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-lea...
> .  That's actually the v2 and most google hits send you to a (now
> removed v1), so it's a little difficult to find.
>
> In any case, the results of using Microsoft's javascript memory leak
> detector are basically unintelligible in a regular production mode GWT
> compile.  I had more luck when compiling with -style PRETTY
> (obviously), and I also turned on -draftCompile.  I think I remember
> that -draftCompile did less inlining, which made the generated
> javascript closer to our Java code.  This was important because the
> output of the tool is basically a series of leaks, like:
>
>  DIV leaked due to onclick from stack XYZ
>
> where "XYZ" is a javascript stack trace of where the onclick event
> handler was set.  By clicking back up the stack you can generally get
> a reasonable idea of which widget in your application is causing the
> problem.
>
> At this point, I didn't actually trust the tool - so from a
> methodology perspective my first step was to validate the tools
> output.
>
> I commented out code and otherwise configured our application down to
> a bare-bones "login, display a couple of things, logout" script that I
> could run in a loop.  Having done so, I could demonstrate that:
>
> a) that operational loop actually leaked in IE
> b) the tool reported about 15 elements as being leaked
>
> Then, I proceeded to ... try to "fix" those leaks.
>
> First attempt was to click the ClickListener (or ClickHandler or
> KeyPressHandler or whatever).  I mean, calling Handler.remove(), or
> removeClickListener() during onDetach().  My theory was that my
> ClickHandler was a Java inner class and it somehow just had an inline
> reference to the parent object and etc.
>
> No joy.  The tool still reported it as a leak.  I've since spent a lot
> more time going through GWT's implementation of event handling and
> it's pretty clear that:
>
> a) the intent is to not have to do this; and
> b) it doesn't set element.onclick = null
>
> I understand the argument with b) is that you don't have to.  I wasn't
> feeling very trusting at this point (I had a memory leak tool from
> Microsoft that seemed to disagree with that), so I thought I'd test
> it.
>
> Readinghttp://javascript.crockford.com/memory/leak.htmlgave me an
> idea, so I wrote a little helper method:
>
>   public native static void purgeEventHooks( Element elem, boolean
> recurse ) /*-{
>     try {
>       elem.onclick = null;
>       elem.ondblclick = null;
>       elem.onmousedown = null;
>       elem.onmouseup = null;
>       elem.onmouseover = null;
>       elem.onmouseout = null;
>       elem.onmousemove = null;
>       elem.onkeydown = null;
>       elem.onkeypress = null;
>       elem.onkeyup = null;
>       elem.onchange = null;
>       elem.onfocus = null;
>       elem.onblur = null;
>       elem.onlosecapture = null;
>       elem.onscroll = null;
>       elem.onload = null;
>       elem.onerror = null;
>       elem.onmousewheel = null;
>       elem.oncontextmenu = null;
>       elem.onpaste = null;
>
>       if (recurse) {
>         var a = elem.childNodes;
>         if (a) {
>             l = a.length;
>             for (i = 0; i < l; i += 1) {
>                 purgeEventHooks(elem.childNodes[i], recurse);
>             }
>         }
>       }
>     } catch( e ) {
>       //  ignore
>     }
>   }-*/;
>
> And then proceeded to call it from onDetach() on the "leaked" element.
>
> Aha - magic -- the Microsoft javascript leak tool no longer reports
> that element as a leak!
>
> However, it seems quite possible that all I've managed to do is fool
> the leak tool, as opposed to actually fix any leak.  So I resolve to
> fix all of the leaks on this code path.  I eventually managed to do
> this (gosh, it was painful -- mostly because it's very difficult from
> a javascript stacktrace to where onclick was called to work out which
> custom GWT widget is ultimately involved).
>
> Now I have the leak tool reporting no leaks.  So, I turn it off and
> put the application back into a loop in Internet Explorer --- half
> expecting to see it still leaking tens of megabytes and being back
> where I started having wasted several days.  But... it didn't leak.
> IE memory usage varied by a few hundred KB (up and down) throughout
> the course of a long test, but no memory leaks.
>
> Soooo - now I'm at a point where I trust the leak tool, and I think
> there's some kind of really fundamental problem in this theory that
> you don't have to clear DOM element event hooks.  So I build a "hello
> world" type GWT application intending to, you know, prove this to the
> world.
>
> No joy.  In that application, (which was just a button added and
> removed from a SimplePanel on the RootPanel), it wasn't necessary to
> clear the DOM element event hooks to have IE clear everything up.  So
> there's some "more complicated than trivial" case in which this is
> triggered.  Unfortunately.  Also unfortunately, I have no-where near
> the kind of time to go back and trial-and-error work out what that
> condition is.
>
> So, now I can't explain why clearing the handlers helps, but I can say
> that it does.  And I am faced with the really daunting task of trying
> to go through our app and explicitly clear event handlers onDetach.
>
> Don't really want to do that -- actually, all I really want to do is
> have Widget.onDetach() call, basically DOM.unsinkEvents().
>
> Mucking around in the GWT code, I find a hook that will let me do
> that.  This is a bit... well, 'distasteful'.  But, it worked for me
> and got us out of a tight spot / will also let us clear up some of the
> other mitigation code we've had in place around this issue.
>
> The observation is that DOM.setElementListener is called on attach
> with a value, and on detach with null.  And it calls into an abstract
> DOMImpl method that I can override with deferred binding (.gwt.xml
> <replace-with>).
>
> The implementation of DOMImpl that I use looks like this:
>
> public class NoMemLeaksDOMImplIE8 extends
> com.google.gwt.user.client.impl.DOMImplIE8 {
>
>   public NoMemLeaksDOMImplIE8() {
>     super();
>   }
>
>   static native void backupEventBits(Element elem) /*-{
>     elem.__eventBits_apptio = elem.__eventBits;
>   }-*/;
>
>   static native int getEventBitsBackup(Element elem) /*-{
>     return (elem.__eventBits_apptio || 0);
>   }-*/;
>
>   static void hookEventListenerChange( DOMImpl impl, Element elem,
> EventListener listener ) {
>     if( listener == null ) {
>       //  Called from onDetach() for Widget (among other places).
>       //
>       //  Basically, we're going to be detached at this point.
>       //  So.... set all event handlers to null to avoid IE
>       //  circular reference memory leaking badness
>       backupEventBits( elem );
>       impl.sinkEvents(elem,0);
>     } else {
>       int backup = getEventBitsBackup( elem );
>       if( backup != 0 ) {
>         impl.sinkEvents( elem, backup );
>       }
>     }
>   }
>
>   public void setEventListener( Element elem, EventListener listener)
> {
>     super.setEventListener( elem, listener );
>     hookEventListenerChange( this, elem, listener );
>   }
>
> }
>
> Note that I have to back up the event bits & restore them in case the
> element is re-attached back into the DOM later (otherwise it won't get
> any of the events it is expecting).
>
> In any case, your actual mileage may vary, but this helps us a LOT.
>
> Lessons learned:
>
> 1) Microsoft javascript memory leak detection tool rocks.  It is
> available from 
> here:http://blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-lea...
> It has an automation mode where you could put an "zero leaks"
> assertion as part of an automated test.
>
> 2) There is some way you can set up a GWT app so that you need to
> clear event handlers in order to avoid leaks in IE6, IE7 & IE8.  No
> plain GWT manipulations I found (removing the clicklisteners, etc)
> helped at all.  When you get into this state, basically every single
> widget you have with an event listener will leak.  Microsoft's leak
> tool isn't lying, but neither can I explain why or what it is that
> pushes the app over this threshold.
>
> 3) You can successfully hack the DOM Impl to have Widget.onDetach()
> clear the event hooks for you, meaning you don't have to refactor all
> your code to follow this pattern.
>
> I hope this helps someone.
>
> - Paul

-- 
You received this message because you are subscribed to the Google Groups 
"Google Web Toolkit" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/google-web-toolkit?hl=en.

Reply via email to