-
Bug
-
Resolution: Won't Fix
-
P3
-
1.4.2, 6u10
-
x86
-
windows_2000, windows_xp
Name: jk109818 Date: 08/15/2003
FULL PRODUCT VERSION :
java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)
FULL OS VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
A DESCRIPTION OF THE PROBLEM :
The static variable com.sun.java.swing.plaf.windows.WindowsPopupMenuUI.mnemonicListener holds onto a reference to the opening windows JRootPane in the variable "repaintRoot". This means that any JFrame/JDialog that you open a popup menu on will not get garbage collected until after you open a popup menu on ANOTHER JFrame/JDialog.
This reference either needs to be cleaned up when the popup menu's window (or parent's) closes or it needs to be held onto via a WeakReference.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Open a JFrame which has a popupmenu (assume you have at least one other JFrame open). Open the popupmenu. Close the JFrame.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
com.sun.java.swing.plaf.windows.WindowsPopupMenuUI.mnemonicListener.repaintRoot should be cleaned up.
ACTUAL -
The JFrame instance will remain in memory until another popupmenu on another window is activated.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
The following example shows the problem and cleans it up. Notice the call to
Object anObject = SafelyGetReflectedField("com.sun.java.swing.plaf.windows.WindowsPopupMenuUI", "mnemonicListener", null);
if (null != anObject)
{
SafelySetReflectedFieldToNull(anObject.getClass(), "repaintRoot", anObject);
}
public class SimpleUI extends javax.swing.JFrame
{
/** Creates new form SimpleUI */
public SimpleUI()
{
initComponents();
}
private void initComponents()
{
javax.swing.JMenu aJMenu_File = new javax.swing.JMenu("File");
aJMenu_File.setMnemonic('F');
javax.swing.Action aTestAction = new javax.swing.AbstractAction()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
testPerformed(evt);
}
};
aTestAction.putValue(aTestAction.NAME, "Test");
aTestAction.putValue(aTestAction.MNEMONIC_KEY, new Integer((int) 'T'));
aTestAction.putValue(aTestAction.ACCELERATOR_KEY, javax.swing.KeyStroke.getKeyStroke("ctrl T"));
javax.swing.JMenuItem aJMenuItem_Test = new javax.swing.JMenuItem(aTestAction);
aJMenu_File.add(aJMenuItem_Test);
javax.swing.JMenuBar aJMenuBar = new javax.swing.JMenuBar();
aJMenuBar.add(aJMenu_File);
setJMenuBar(aJMenuBar);
final javax.swing.JPopupMenu aJPopupMenu = new javax.swing.JPopupMenu();
javax.swing.JMenu aJMenu = new javax.swing.JMenu("File");
javax.swing.JMenuItem aJMenuItem = new javax.swing.JMenuItem(aTestAction);
aJMenu.add(aJMenuItem);
aJPopupMenu.add(aJMenu);
getContentPane().setLayout(new java.awt.FlowLayout());
final javax.swing.JButton aJButton_Test = new javax.swing.JButton(aTestAction);
aJButton_Test.addMouseListener(
new java.awt.event.MouseAdapter()
{
public void mousePressed(java.awt.event.MouseEvent e)
{
if (e.isPopupTrigger())
openJPopupMenu(e);
}
public void mouseReleased(java.awt.event.MouseEvent e)
{
if (e.isPopupTrigger())
openJPopupMenu(e);
}
public void openJPopupMenu(java.awt.event.MouseEvent e)
{
aJPopupMenu.show(aJButton_Test, e.getX(), e.getY());
}
}
);
getContentPane().add(aJButton_Test);
javax.swing.Action anAction_Spawn = new javax.swing.AbstractAction("Spawn")
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
spawnPerformed(evt);
}
};
javax.swing.JButton aJButton_Spawn = new javax.swing.JButton(anAction_Spawn);
getContentPane().add(aJButton_Spawn);
javax.swing.Action anAction_Exit = new javax.swing.AbstractAction("Exit")
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
System.exit(0);
}
};
javax.swing.JButton aJButton_Exit = new javax.swing.JButton(anAction_Exit);
getContentPane().add(aJButton_Exit);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
public void show()
{
pack();
addWindowListener(
new java.awt.event.WindowAdapter()
{
public void windowClosed(java.awt.event.WindowEvent e)
{
e.getWindow().removeWindowListener(this);
cleanup();
}
public void windowClosing(java.awt.event.WindowEvent e)
{
e.getWindow().removeWindowListener(this);
dispose();
cleanup();
}
}
);
super.show();
}
private void testPerformed(java.awt.event.ActionEvent evt)
{
setDefaultCloseOperation(super.DISPOSE_ON_CLOSE);
dispose();
spawnPerformed(evt);
}
private void spawnPerformed(java.awt.event.ActionEvent evt)
{
(new SimpleUI()).show();
}
public void cleanup()
{
SwingHacks.CleanupJPopupMenuGlobals(true);
SwingHacks.CleanupJMenuBarGlobals();
}
/**
* @param args the command line arguments
*/
public static void main(String args[])
{
try
{
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
}
new SimpleUI().show();
}
public static class SwingHacks
{
public static void CleanupJPopupMenuGlobals(boolean removeOnlyMenuKeyboardHelpers)
{
try
{
javax.swing.MenuSelectionManager aMenuSelectionManager = javax.swing.MenuSelectionManager.defaultManager();
Object anObject = SafelyGetReflectedField("javax.swing.MenuSelectionManager", "listenerList", aMenuSelectionManager);
if (null != anObject)
{
javax.swing.event.EventListenerList anEventListenerList = (javax.swing.event.EventListenerList) anObject;
Object[] listeners = anEventListenerList.getListenerList();
if (removeOnlyMenuKeyboardHelpers)
{
// This gives us back an Array and the even entries are the
// class type. In this case they are all javax.swing.event.ChangeListeners
// The odd number entries are the instance themselves.
// We were having a problem just blindly removing all of the listeners
// because the next time a popupmenu was show, it wasn't getting dispose (i.e you
// right click and click off to cancel and the menu doesn't go away). We traced
// the memory leak down to this javax.swing.plaf.basic.BasicPopupMenuUI$MenuKeyboardHelper
// holding onto an instance of the JRootPane. Therefore we just remove all of the
// instances of this class and it cleans up fine and seems to work.
Class aClass = Class.forName("javax.swing.plaf.basic.BasicPopupMenuUI$MenuKeyboardHelper");
for (int i = listeners.length - 1; i >= 0; i -= 2)
{
if (aClass.isInstance(listeners[i]))
{
aMenuSelectionManager.removeChangeListener((javax.swing.event.ChangeListener) listeners[i]);
}
}
}
else
{
for (int i = listeners.length - 1; i >= 0; i -= 2)
{
aMenuSelectionManager.removeChangeListener((javax.swing.event.ChangeListener) listeners[i]);
}
}
}
}
catch (Exception e)
{
// e.printStackTrace();
}
try
{
javax.swing.ActionMap anActionMap = (javax.swing.ActionMap) javax.swing.UIManager.getLookAndFeelDefaults().get("PopupMenu.actionMap");
while (anActionMap != null)
{
Object[] keys = { "press", "release" };
boolean anyFound = false;
for (int i = 0; i < keys.length; i++)
{
Object aKey = keys[i];
Object aValue = anActionMap.get(aKey);
anyFound = anyFound || aValue != null;
anActionMap.remove(aKey);
}
if (!anyFound)
{
break;
}
anActionMap = anActionMap.getParent();
}
}
catch (Exception e)
{
// e.printStackTrace();
}
SafelySetReflectedFieldToNull("javax.swing.plaf.basic.BasicPopupMenuUI", "menuKeyboardHelper", null);
Object anObject = SafelyGetReflectedField("com.sun.java.swing.plaf.windows.WindowsPopupMenuUI", "mnemonicListener", null);
if (null != anObject)
{
SafelySetReflectedFieldToNull(anObject.getClass(), "repaintRoot", anObject);
}
}
private static void SafelySetReflectedFieldToNull(Class aClass, String aFieldName, Object anObject)
{
try
{
java.lang.reflect.Field aField = aClass.getDeclaredField(aFieldName);
aField.setAccessible(true);
aField.set(anObject, null);
}
catch (Exception e)
{
// System.out.println(e)
}
}
private static void SafelySetReflectedFieldToNull(String aClassName, String aFieldName, Object anObject)
{
try
{
Class aClass = Class.forName(aClassName);
SafelySetReflectedFieldToNull(aClass, aFieldName, anObject);
}
catch (Exception e)
{
// System.out.println(e)
}
}
private static Object SafelyGetReflectedField(String aClassName, String aFieldName, Object anObject)
{
try
{
Class aClass = Class.forName(aClassName);
java.lang.reflect.Field aField = aClass.getDeclaredField(aFieldName);
aField.setAccessible(true);
return aField.get(anObject);
}
catch (Exception e)
{
// System.out.println(e)
return null;
}
}
public static void CleanupJMenuBarGlobals()
{
SafelySetReflectedFieldToNull("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor", "root", null);
SafelySetReflectedFieldToNull("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor", "winAncestor", null);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Using reflection, one can null-out the variable so the your window will get garbage collected. In the above example the lines,
Object anObject = SafelyGetReflectedField("com.sun.java.swing.plaf.windows.WindowsPopupMenuUI", "mnemonicListener", null);
if (null != anObject)
{
SafelySetReflectedFieldToNull(anObject.getClass(), "repaintRoot", anObject);
}
are performing the desired behavior. This needs to be invoked when a JFrame/JDialog window is being closed.
(Incident Review ID: 198829)
======================================================================
Additional input:
================
The evaluation is insufficient. Just because the problem is not cumulative, it does not mean the the effects are not severe. In our case, they are.
Consider: I have a Frame. This frame is created, disposed, and managed by a third party library. Furthermore, this frame holds references to items that are out of my control. Imagine that it is megabytes of data. Now we encounter WindowsPopupMenuUI. Now the frame is never going to be disposed--not unless the circumstances are exactly right or Swing's Windows LAF is hacked the right way. If the frame is large enough, an OutOfMemoryError can easily be thrown.
The solution, from Oracle's end, is very simple: just make MnemonicListener.repaintRoot a WeakReference. None of the rest of the code has to change.