EVALUATION
Evaluation Done be Leonid <###@###.###>
I have investigated the issue and found the root cause why instances of JDialog don't get GC'ed. Actually it is KeyboardManager who holds live references to them. I'll try to explain how.
In the test, we create a visual form (e.g. JFileChooser) that we put into a JDialog. JFileChooser in Metal and Windows LaF's has two or three JLabels binded with other components through 'setLabelFor' method, and having key mnemonic set via 'setDisplayedMnemonic'. On dialog creation, it registers the bindings in KeyboardManager, here is the call stack:
registerKeyStroke():83, javax.swing.KeyboardManager
registerWithKeyboardManager():2163, javax.swing.JComponent
registerWithKeyboardManager():2088, javax.swing.JComponent
addNotify():4668, javax.swing.JComponent
addNotify():2579, java.awt.Container
addNotify():4665, javax.swing.JComponent
addNotify():2579, java.awt.Container
addNotify():4665, javax.swing.JComponent
addNotify():2579, java.awt.Container
addNotify():4665, javax.swing.JComponent
addNotify():2579, java.awt.Container
addNotify():4665, javax.swing.JComponent
addNotify():2579, java.awt.Container
addNotify():4665, javax.swing.JComponent
addNotify():735, javax.swing.JRootPane
addNotify():2579, java.awt.Container
addNotify():635, java.awt.Window
addNotify():744, java.awt.Dialog
pack():663, java.awt.Window
createDialog():780, javax.swing.JFileChooser
showDialog():714, javax.swing.JFileChooser
showOpenDialog():626, javax.swing.JFileChooser
doAttempt():65, TwentyThousandTest
main():37, TwentyThousandTest
When registering a keystroke, KeyboardManager puts the top parent window (JDialog in our case) in the 'containerMap' hashmap. After the dialog closes, the key bindings get unregistered:
unregisterKeyStroke():170, javax.swing.KeyboardManager
unregisterWithKeyboardManager():2167, javax.swing.JComponent
unregisterWithKeyboardManager():2137, javax.swing.JComponent
removeNotify():4687, javax.swing.JComponent
removeNotify():2606, java.awt.Container
removeNotify():4681, javax.swing.JComponent
removeNotify():2606, java.awt.Container
removeNotify():4681, javax.swing.JComponent
removeNotify():2606, java.awt.Container
removeNotify():4681, javax.swing.JComponent
removeNotify():2606, java.awt.Container
removeNotify():4681, javax.swing.JComponent
removeNotify():2606, java.awt.Container
removeNotify():4681, javax.swing.JComponent
removeNotify():750, javax.swing.JRootPane
removeNotify():2606, java.awt.Container
removeNotify():645, java.awt.Window
run():973, java.awt.Window$1DisposeAction
dispatch():199, java.awt.event.InvocationEvent
dispatchEvent():597, java.awt.EventQueue
pumpOneEventForFilters():273, java.awt.EventDispatchThread
pumpEventsForFilter():183, java.awt.EventDispatchThread
pumpEventsForHierarchy():173, java.awt.EventDispatchThread
pumpEvents():168, java.awt.EventDispatchThread
pumpEvents():160, java.awt.EventDispatchThread
run():121, java.awt.EventDispatchThread
The JDialog gets removed from the map, and everything is fine so far. But later on, for the next iteration (or attempt), if 'UPDATE_UI_EACH_INTERVAL' set to 'true' we call 'updateUI' on JFileChooser. If we haven't just created a new instance of JFileChooser ('ALWAYS_NEW_INSTANCE' was set to 'false'), it still refers to the already closed JDialog as its parent. So this causes the key bindings to register again through another call stack:
registerKeyStroke():83, javax.swing.KeyboardManager
registerWithKeyboardManager():2163, javax.swing.JComponent
registerWithKeyboardManager():2088, javax.swing.JComponent
componentInputMapChanged():2158, javax.swing.JComponent
put():77, javax.swing.ComponentInputMap
installKeyboardActions():363, javax.swing.plaf.basic.BasicLabelUI
propertyChange():412, javax.swing.plaf.basic.BasicLabelUI
firePropertyChange():339, java.beans.PropertyChangeSupport
firePropertyChange():276, java.beans.PropertyChangeSupport
firePropertyChange():7846, java.awt.Component
setLabelFor():1014, javax.swing.JLabel
installComponents():221, javax.swing.plaf.metal.MetalFileChooserUI
installUI():136, javax.swing.plaf.basic.BasicFileChooserUI
installUI():124, javax.swing.plaf.metal.MetalFileChooserUI
setUI():668, javax.swing.JComponent
updateUI():1762, javax.swing.JFileChooser
doAttempt():56, TwentyThousandTest
main():37, TwentyThousandTest
An instance of JDialog (the chooser's parent) gets stored in the KeyboardManager's 'containerMap' again and lives there forever, because on the next step we create and open a new JDialog for JFileChooser, and the cycle repeats. So every iteration leaves an instance of JDialog in the 'containerMap'. In turn, KeyboardManager has a static field 'currentManager' where it keeps an instance of itself so the 'containerMap' also doesn't get collected by GC, and hundreds of JDialogs fill up the memory.
That's all! As for possible ways of fixing, I think, since we cannot reopen an already closed JFileChooser's dialog, it seems to be a good idea to remove all components on dialog closing. It will prevent the leak from appearing in real applications, although it is quite unlikely to happen as it is, to my mind.
|