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

Significant memory leak in BasicComboBoxUI and MetalComboBoxButton

    XMLWordPrintable

Details

    • b14
    • x86
    • windows_xp

    Description

      FULL PRODUCT VERSION :
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_10-b03)
      Java HotSpot(TM) Server VM (build 1.5.0_10-b03, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      SunOS nys31a-016 5.8 Generic_117000-05 sun4u sparc SUNW,Sun-Fire-480R

      A DESCRIPTION OF THE PROBLEM :
      javax.swing.plaf.basic.BasicComboBoxUI and javax.swing.plaf.metal.MetalComboBoxButton use a CellRenderer pane to paint the renderer component retrieved via getListCellRenderer from the JList instance. CellRendererPane paints the component but it never removes it from its component hierarchy. CellRendererPane does ADD the painted component automatically at the beginning of its paint method but _does_not automatically remove the component at the end of its paint method - the component MUST be removed manually by the caller of the paint method.

      This has HUGE impact. as JList can produce (and it does in our case) dynamic renderer component instances in getListCellRenderer method. Each one of these instances will linger in memory because it remains in the component hierarchy of the CellRendererPane.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      BasicComboBoxUI.java

          public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
              ListCellRenderer renderer = comboBox.getRenderer();
              Component c;

              if ( hasFocus && !isPopupVisible(comboBox) ) {
                  c = renderer.getListCellRendererComponent( listBox,
                                                             comboBox.getSelectedItem(),
                                                             -1,
                                                             true,
                                                             false );
              }
              else {
                  c = renderer.getListCellRendererComponent( listBox,
                                                             comboBox.getSelectedItem(),
                                                             -1,
                                                             false,
                                                             false );
                  c.setBackground(UIManager.getColor("ComboBox.background"));
              }
              c.setFont(comboBox.getFont());
              if ( hasFocus && !isPopupVisible(comboBox) ) {
                  c.setForeground(listBox.getSelectionForeground());
                  c.setBackground(listBox.getSelectionBackground());
              }
              else {
                  if ( comboBox.isEnabled() ) {
                      c.setForeground(comboBox.getForeground());
                      c.setBackground(comboBox.getBackground());
                  }
                  else {
                      c.setForeground(DefaultLookup.getColor(
                               comboBox, this, "ComboBox.disabledForeground", null));
                      c.setBackground(DefaultLookup.getColor(
                               comboBox, this, "ComboBox.disabledBackground", null));
                  }
              }

              // Fix for 4238829: should lay out the JPanel.
              boolean shouldValidate = false;
              if (c instanceof JPanel) {
                  shouldValidate = true;
              }

              currentValuePane.paintComponent(g,c,comboBox,bounds.x,bounds.y,
                                              bounds.width,bounds.height, shouldValidate);

      ///////////// BUG ///////// Component c is never removed from the currentValuePane
          }


      MetalComboBoxButton.java


          public void paintComponent( Graphics g ) {
              boolean leftToRight = MetalUtils.isLeftToRight(comboBox);

              // Paint the button as usual
              super.paintComponent( g );

              Insets insets = getInsets();

              int width = getWidth() - (insets.left + insets.right);
              int height = getHeight() - (insets.top + insets.bottom);

              if ( height <= 0 || width <= 0 ) {
                  return;
              }

              int left = insets.left;
              int top = insets.top;
              int right = left + (width - 1);
              int bottom = top + (height - 1);

              int iconWidth = 0;
              int iconLeft = (leftToRight) ? right : left;

              // Paint the icon
              if ( comboIcon != null ) {
                  iconWidth = comboIcon.getIconWidth();
                  int iconHeight = comboIcon.getIconHeight();
                  int iconTop = 0;

                  if ( iconOnly ) {
                      iconLeft = (getWidth() / 2) - (iconWidth / 2);
                      iconTop = (getHeight() / 2) - (iconHeight / 2);
                  }
                  else {
      if (leftToRight) {
      iconLeft = (left + (width - 1)) - iconWidth;
      }
      else {
      iconLeft = left;
      }
                      iconTop = (top + ((bottom - top) / 2)) - (iconHeight / 2);
                  }

                  comboIcon.paintIcon( this, g, iconLeft, iconTop );

                  // Paint the focus
                  if ( comboBox.hasFocus() && (!MetalLookAndFeel.usingOcean() ||
                                               comboBox.isEditable())) {
                      g.setColor( MetalLookAndFeel.getFocusColor() );
                      g.drawRect( left - 1, top - 1, width + 3, height + 1 );
                  }
              }

              if (MetalLookAndFeel.usingOcean()) {
                  // With Ocean the button only paints the arrow, bail.
                  return;
              }

              // Let the renderer paint
              if ( ! iconOnly && comboBox != null ) {
                  ListCellRenderer renderer = comboBox.getRenderer();
                  Component c;
                  boolean renderPressed = getModel().isPressed();
                  c = renderer.getListCellRendererComponent(listBox,
                                                            comboBox.getSelectedItem(),
                                                            -1,
                                                            renderPressed,
                                                            false);
                  c.setFont(rendererPane.getFont());

                  if ( model.isArmed() && model.isPressed() ) {
                      if ( isOpaque() ) {
                          c.setBackground(UIManager.getColor("Button.select"));
                      }
                      c.setForeground(comboBox.getForeground());
                  }
                  else if ( !comboBox.isEnabled() ) {
                      if ( isOpaque() ) {
                          c.setBackground(UIManager.getColor("ComboBox.disabledBackground"));
                      }
                      c.setForeground(UIManager.getColor("ComboBox.disabledForeground"));
                  }
                  else {
                      c.setForeground(comboBox.getForeground());
                      c.setBackground(comboBox.getBackground());
                  }


                  int cWidth = width - (insets.right + iconWidth);
                  
                  // Fix for 4238829: should lay out the JPanel.
                  boolean shouldValidate = false;
                  if (c instanceof JPanel) {
                      shouldValidate = true;
                  }
                  
      if (leftToRight) {
      rendererPane.paintComponent( g, c, this,
      left, top, cWidth, height, shouldValidate );
      }
      else {
      rendererPane.paintComponent( g, c, this,
      left + iconWidth, top, cWidth, height, shouldValidate );
      }

      //// BUG //// c is never removed from rendererPane
              }
          }

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The renderer component gets removed from the component hierarchy of the CellRendererPane after its painting is done. No memory leaks are introduced

      ACTUAL -
      ALL instances of a renderer linger in memory and are never garbage collected. JVM runs out of memory due to this memory leak.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      I provided your own source code pinpointing the problematic areas
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      BasicComboBoxUI.java

          public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
              ListCellRenderer renderer = comboBox.getRenderer();
              Component c;

              if ( hasFocus && !isPopupVisible(comboBox) ) {
                  c = renderer.getListCellRendererComponent( listBox,
                                                             comboBox.getSelectedItem(),
                                                             -1,
                                                             true,
                                                             false );
              }
              else {
                  c = renderer.getListCellRendererComponent( listBox,
                                                             comboBox.getSelectedItem(),
                                                             -1,
                                                             false,
                                                             false );
                  c.setBackground(UIManager.getColor("ComboBox.background"));
              }
              c.setFont(comboBox.getFont());
              if ( hasFocus && !isPopupVisible(comboBox) ) {
                  c.setForeground(listBox.getSelectionForeground());
                  c.setBackground(listBox.getSelectionBackground());
              }
              else {
                  if ( comboBox.isEnabled() ) {
                      c.setForeground(comboBox.getForeground());
                      c.setBackground(comboBox.getBackground());
                  }
                  else {
                      c.setForeground(DefaultLookup.getColor(
                               comboBox, this, "ComboBox.disabledForeground", null));
                      c.setBackground(DefaultLookup.getColor(
                               comboBox, this, "ComboBox.disabledBackground", null));
                  }
              }

              // Fix for 4238829: should lay out the JPanel.
              boolean shouldValidate = false;
              if (c instanceof JPanel) {
                  shouldValidate = true;
              }

              currentValuePane.paintComponent(g,c,comboBox,bounds.x,bounds.y,
                                              bounds.width,bounds.height, shouldValidate);

      // FIX // currentValuePane.remove(c);
          }


      MetalComboBoxButton.java


          public void paintComponent( Graphics g ) {
              boolean leftToRight = MetalUtils.isLeftToRight(comboBox);

              // Paint the button as usual
              super.paintComponent( g );

              Insets insets = getInsets();

              int width = getWidth() - (insets.left + insets.right);
              int height = getHeight() - (insets.top + insets.bottom);

              if ( height <= 0 || width <= 0 ) {
                  return;
              }

              int left = insets.left;
              int top = insets.top;
              int right = left + (width - 1);
              int bottom = top + (height - 1);

              int iconWidth = 0;
              int iconLeft = (leftToRight) ? right : left;

              // Paint the icon
              if ( comboIcon != null ) {
                  iconWidth = comboIcon.getIconWidth();
                  int iconHeight = comboIcon.getIconHeight();
                  int iconTop = 0;

                  if ( iconOnly ) {
                      iconLeft = (getWidth() / 2) - (iconWidth / 2);
                      iconTop = (getHeight() / 2) - (iconHeight / 2);
                  }
                  else {
      if (leftToRight) {
      iconLeft = (left + (width - 1)) - iconWidth;
      }
      else {
      iconLeft = left;
      }
                      iconTop = (top + ((bottom - top) / 2)) - (iconHeight / 2);
                  }

                  comboIcon.paintIcon( this, g, iconLeft, iconTop );

                  // Paint the focus
                  if ( comboBox.hasFocus() && (!MetalLookAndFeel.usingOcean() ||
                                               comboBox.isEditable())) {
                      g.setColor( MetalLookAndFeel.getFocusColor() );
                      g.drawRect( left - 1, top - 1, width + 3, height + 1 );
                  }
              }

              if (MetalLookAndFeel.usingOcean()) {
                  // With Ocean the button only paints the arrow, bail.
                  return;
              }

              // Let the renderer paint
              if ( ! iconOnly && comboBox != null ) {
                  ListCellRenderer renderer = comboBox.getRenderer();
                  Component c;
                  boolean renderPressed = getModel().isPressed();
                  c = renderer.getListCellRendererComponent(listBox,
                                                            comboBox.getSelectedItem(),
                                                            -1,
                                                            renderPressed,
                                                            false);
                  c.setFont(rendererPane.getFont());

                  if ( model.isArmed() && model.isPressed() ) {
                      if ( isOpaque() ) {
                          c.setBackground(UIManager.getColor("Button.select"));
                      }
                      c.setForeground(comboBox.getForeground());
                  }
                  else if ( !comboBox.isEnabled() ) {
                      if ( isOpaque() ) {
                          c.setBackground(UIManager.getColor("ComboBox.disabledBackground"));
                      }
                      c.setForeground(UIManager.getColor("ComboBox.disabledForeground"));
                  }
                  else {
                      c.setForeground(comboBox.getForeground());
                      c.setBackground(comboBox.getBackground());
                  }


                  int cWidth = width - (insets.right + iconWidth);
                  
                  // Fix for 4238829: should lay out the JPanel.
                  boolean shouldValidate = false;
                  if (c instanceof JPanel) {
                      shouldValidate = true;
                  }
                  
      if (leftToRight) {
      rendererPane.paintComponent( g, c, this,
      left, top, cWidth, height, shouldValidate );
      }
      else {
      rendererPane.paintComponent( g, c, this,
      left + iconWidth, top, cWidth, height, shouldValidate );
      }

      //fix/// rendererPane.remove(c);
              }
          }

      Attachments

        Issue Links

          Activity

            People

              psadhukhan Prasanta Sadhukhan
              ndcosta Nelson Dcosta (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:
                Imported:
                Indexed: