Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4907798

MEMORY LEAK: javax.swing.plaf.basic.BasicPopupMenuUI$MenuKeyboardHelper

    Details

    • Subcomponent:
    • Understanding:
      Cause Known
    • CPU:
      x86
    • OS:
      windows_2000

      Description

      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 :
      javax.swing.plaf.basic.BasicPopupMenuUI$MenuKeyboardHelper instances are not being removed from the static instance javax.swing.MenuSelectionManager.defaultManager() until after a new popup menu has been opened. This is causing JFrames to not be garbage collected until after the user opens another popup menu which is very non-intuitive.

      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. The JFrame instance will remain in memory until another popupmenu is activated.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The listener instances should have been de-registered when the popup window was closed (and likewise re-added when opened).
      ACTUAL -
      The JFrame remains in memory until another popupmenu is opened.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      Please run the following test and notice how many listeners are left when the SimpleUI.SwingHacks.CleanupJPopupMenuGlobals method is called.


      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 :
      The following code snipet seems to cleanup the instances correctly. Set the boolean removeOnlyMenuKeyboardHelpers to true in all instances EXCEPT on the final window closing.

            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();
            }
      (Incident Review ID: 198820)
      ======================================================================

        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              jkimsunw Jeffrey Kim (Inactive)
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Imported:
                Indexed: