17 December 2008

Swing and the Leaks (part 1 of ...)

This is the first real article here. I wish I'd have the guts and persistence to finish it off like Kitana does Sub-Zero in Mortal Kombat. I don't even know how I could have remembered that game. Anyways, unless I scout wikipedia for so useless links I'd not finish it off ever.
There I go.

After quite enough years working with user interface I never managed to like building one. It's simply too much detailed process to please the end user. It rarely involves any nice ideas or algorithms, yet worst of all I can not find it challenging thus entertaining.

Swing has walked a long way since it the days it was a separate library. MVC architecture should be considered cool and dandy, yet often it's a source of extra complicity. However, neither the lacks of very rich component support or good layout managers is a real show stopper. De-facto Swing is the GUI library of choice for Java.
What really bothers me however it's the very poor 'leak' control in Swing. I have come across quite a few bugs that simply eat memory. Virtually all of them have some work around but finding a leak is never easy.
This article is about one afternoon when a colleague of mine called me and asked to help him out. It took good 4 hours to track the bugger down and smash it accordingly. The bug, itself, is in
javax.swing.KeyboardManager or JComponent.registerKeyboardAction(...)
To summarize the problem (before any code) if you register a keyboardAction in invisible Window (JWindow) the component hierarchy will remain unclaimed by the garbage collector until the parent (getOwner()) window is disposed (not the window, itself)

Some code. We have a JWindow (no decoration, thanks) which we can drag around the screen.



Unfortunately (fortunately), when I started writing the supposedly simple test case I fell upon some other(!) unpleasant leaks. In the end I decided to present a fully blown example of not so terribad small swing application with a few other cool perks to show like using WeakReference, Timer (swing ones) and proper threading (or virtually lack of). As a final bonus the example comes with code to cope with all of the leaks (swingHacks method).
But first things first. Link to an executable jar, so you can see it running. There is one more web-start option to see the small application started with all the security checks enabled and sometimes bypassed. Perhaps, I will go back to the code in another article.
And now the leaks, there are a few types of them.
  • Worst of all, certainly, a permanent leak that nothing may not help to solve (save ending the java process);
  • A weird one that requires to create a new AbstractButton subclass to fix;
  • Another weirdo that would require to switch between applications and focus lost event to occur
The bug that made me actually start all that off can be reproduced by launching the application and leave the first option ("Fix KeyStroke adding") unchecked. Once a new window is created it won't be reclaimed by the garbage collector until the owner window (in that case the main frame) is also collected.
The root of the problem is finding the top container by the KeyboardManager which in short must be a focusableWindow. The code below illustrates:

(full source of the class)
So adding key binding early on with connect the key with the owning frame instead of the target window (see the constructor of DragWindow). All that might look fine because in the end the same top container (i.e. frame in our case) will be used to release the connection. Alas, any reregistering of the key bindings (popup menu on the 'x' button is an example of) would recreate those binding with the new already visible window (DragWindow). Reregister of the bindings doesn't remove the existing one hoping to simply override them. Since the new topContainer is different the previous binding is never released. It'd help if KeyboardManager attempts to unregister a key binding first, but that's not the case. Thus, we got a fat leak here.
Solution (work-around actually) to the problem is never register in a non-focusable window that might change its (
focusable) state. Basically non-focusable window is any non-dialog/non-frame window that's not yet visible or doesn't have any components. to receive focus. It's tricky but the best practise is registering the key bindings after the window is opened.
How to prevent such leaks: my rule of the thumb when writing any library code is using weak references for the stuff my code won't own. This includes simple listeners. As a side effect that prohibits idioms like addXXXListener(new XXXListener(){}); that idiom, itself, doesn't allow de-register. However, since the weak reference to the XXXListener is usually very quickly reclaimed by the garbage collector the developers notice the problem early on and assign
the listener to some member reference which can be unregistered in the end of the life-cycle of the component (not only java.awt.Component).

The second issue comes from javax.swing.ActionPropertyChangeListenerwhich uses weak references to deal with some leaks and includes a nice warning

* WARNING WARNING WARNING WARNING WARNING WARNING:
* Do NOT create an annonymous inner class that extends this! If you do
* a strong reference will be held to the containing class, which in most
* cases defeats the purpose of this class.
However, there is a major flaw in the entire class: while it keeps a weak reference to the control (any JComponent subclass) it also keeps a strong reference to the action. It's general practice to create inner classes for action implementation which forfeits the purpose of the class. Well, not entirely: the strong references are kept in a WeakReference subclass (OwnedWeakReference) that later goes into a reference queue, static queue (yes, leak prone). "static ReferenceQueue queue". Until the queue is expunged which happens in the constructor of the class... which is usually called by AbstractButton.setAction. That's it: to reclaim the stale instances setAction shall be called. This may or not may happen in appropropriate time (i.e. after garbage collector has enqueued the problematic OwnedWeakReference), actually that may happen in inappropriate thread (even SwingWorker thread).
Solution - well, there is no easy solution. The method swingHacks solves the problem but it looks cumbersome at best.
How to prevent - using actions that are not implemented through inner classes (or at least static inner) and hold no reference to the component structure, relying solely on the getSource(), might be a viable option, yet it turns coding into a day time nightmare and it could be truly counter productive. Using weak reference (in static inner classes) to the outer class is possible, yet also unpleasant to code and special care must be taken. Overall there is no elegant solution for this case

Another bug hides in javax.swing.plaf.basic.BasicPopupMenuUI.MenuKeyboardHelper that holds a reference to the InputMap (javax.swing.InputMap) of the popup menu. The reference is kept until a ChangeEvent is fired by java.swing.MenuSelectionManager that happens after a new menu is unfold. The little trick in swingHacks enforces it.

JPopupMenu popup=new JPopupMenu();
popup.show(owner, 0, 0);
popup.requestFocus();
popup.setVisible(false);
The Solution, once again, is in swingHacks and there is no true prevention.
Actually according Sun that's not a bug. Yet, I hope it will be fixed in some future release.
In the best case the non-a-bug may hold reference to quite big structure which effectively prevent garbage collection. We used to have a problem with exactly this bug while loading a 'huge' configuration (loading a new configuration disposes the existing one) that further loads more data and sometimes could crash with notorious OOM exception. In short it halved the maximum available memory under some conditions.

Last leak
It's related to the KeyboardFocusManager and well discussed at Sun bug forums.
Sun says it has been fixed in java 7(b17) but not backported, so for now you can leave with the code in swingHacks [again]. There is no real prevention or solution to my knowledge. Well, KeyboardFocusManager could have used some help while disposing windows (which swingHack basically tries to enforce), alas not the case.

Hopefully, the writeup was not horrifyingly boring and you managed to enjoy at least a little.

Next article can go deeper into the sample started-as-simple-test-code or no...
Anyways I promise some nice article about NIO, files and some more IO stuff.

1 comment:

  1. Hey,

    Thanks for the visit!! I'm subscribing to your blog's feed!

    ReplyDelete