FULL PRODUCT VERSION :
java version "1.5.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_01-b08)
Java HotSpot(TM) Client VM (build 1.5.0_01-b08, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
JFrame is not garbage collected in minor collection if JPopupMenu is displayed.
If a frame is opened, a popup menu is invoked, and frame is closed, the frame will not be collected during minor collection. Full GC is required to collect it.
This creates a problem in my application as tenured generation keeps growing when users keep opening and closing windows (a user can repeat it couple hundred times), requiring full GC eventually. It freezes application for several seconds. This is unacceptable for our real time application.
I have applied all hacks that were recommended (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4907798), but still having this problem.
Please also note that I am trying to bypass another bug (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4649647) that requiring me to switch focus to another window once the frame is opened.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Test 1
1) Run the program attached below with parameters -XX:+PrintGCDetails -XX:+PrintGCTimeStamps to trace GC calls. Two frames will appear.
2) Select "Open Frame" button on one of them. "Test" frame will appear
3) Close "Test" frame and keep switching focus between two remaining frames by clicking on them.
4) Observe that "Test" frame is collected during next minor GC ("finalized" is printed).
Test 2
1) Run the program attached below with parameters -XX:+PrintGCDetails -XX:+PrintGCTimeStamps to trace GC calls. Two frames will appear.
2) Select "Open Frame" button on one of them. "Test" frame will appear
3) Click "Swing is frustrating" to display popup menu.
4) Select "Select me" item.
5) Close "Test" frame and keep switching focus between two remaining frames by clicking on them.
6) Observe that "Test" frame is NOT collected during next minor GC.
7) Press "GC" button to invoke full GC. Observe that "Test" frame is now collected.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected frame to be collected in minor collection in Test 2
ACTUAL -
Frame was not collected in minor collection in Test 2
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
public class TestMemLeak extends javax.swing.JFrame
{
public TestMemLeak()
{
initComponents();
java.util.Timer idletimer = new java.util.Timer();
idletimer.schedule(new java.util.TimerTask()
{
public void run()
{
int forcegc[] = new int[10240];
}
}, 0, 3000);
}
private void initComponents()
{
jButton1 = new javax.swing.JButton();
jButton2 = new javax.swing.JButton();
addWindowListener(new java.awt.event.WindowAdapter()
{
public void windowClosing(java.awt.event.WindowEvent evt)
{
exitForm(evt);
}
});
jButton1.setText("Open Frame");
jButton1.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jButton1ActionPerformed(evt);
}
});
jButton2.setText("GC");
jButton2.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
System.gc();
}
});
getContentPane().add(jButton1, java.awt.BorderLayout.NORTH);
getContentPane().add(jButton2, java.awt.BorderLayout.SOUTH);
pack();
}
TestMemDialog testdlg;
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
{
javax.swing.SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
testdlg = new TestMemDialog();
testdlg.setBounds(100, 100, 200, 200);
testdlg.setVisible(true);
testdlg = null;
}
});
}
private void exitForm(java.awt.event.WindowEvent evt)
{
System.exit(0);
}
public static void main(String args[])
{
// windows l&f
try
{
javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
}
catch (Exception exc)
{
}
TestMemLeak testMemLeak = new TestMemLeak();
testMemLeak.setLocation(10, 10);
testMemLeak.setVisible(true);
TestMemLeak testMemLeak2 = new TestMemLeak();
testMemLeak2.setLocation(200, 10);
testMemLeak2.setVisible(true);
}
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
}
class TestMemDialog extends javax.swing.JFrame
{
public void finalize()
{
System.err.println("Finalized");
}
public TestMemDialog()
{
super("Test");
initComponents();
}
private void initComponents()
{
this.setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
this.getContentPane().setLayout(new java.awt.BorderLayout());
final javax.swing.JButton btn = new javax.swing.JButton("Swing is frustrating");
this.getContentPane().add(btn, java.awt.BorderLayout.CENTER);
jPopupMenu1 = new javax.swing.JPopupMenu();
jMenuItem1 = new javax.swing.JMenuItem();
jMenuItem1.setText("Select me");
jPopupMenu1.add(jMenuItem1);
this.addWindowListener(new java.awt.event.WindowAdapter()
{
public void windowOpened(java.awt.event.WindowEvent evt)
{
btn.requestFocus();
}
public void windowClosing(java.awt.event.WindowEvent evt)
{
Close();
removeWindowListener(this);
}
});
btn.addActionListener(new java.awt.event.ActionListener()
{
public void actionPerformed(java.awt.event.ActionEvent evt)
{
jMousePressed();
}
});
}
private void Close()
{
CleanupJPopupMenuGlobals(true);
dispose();
}
private void jMousePressed()
{
jPopupMenu1.show(this, 0, 0);
}
private javax.swing.JPopupMenu jPopupMenu1;
private javax.swing.JMenuItem jMenuItem1;
private 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);
}
safelySetReflectedFieldToNull("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor", "root", null);
safelySetReflectedFieldToNull("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor", "winAncestor", null);
}
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.err.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.err.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.err.println(e);
return null;
}
}
}
---------- END SOURCE ----------
###@###.### 2005-1-17 10:02:08 GMT