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

Significant memory leak in BasicListUI

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P3 P3
    • None
    • 6
    • client-libs

      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 :
      Microsoft Windows XP [Version 5.1.2600]

      A DESCRIPTION OF THE PROBLEM :
      BasicListUI uses a CellRenderer pane to paint the renderer component retrieved via getListCellRenderer from the JList instance. CellRendererPane paint the component but it never removes it from its component hierarchy.

      This has huge impact. Jlist can produce _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.

      Methods with a bug in BasicListUI:

      protected void updateLayoutState()
      protected void paintCell()

      This bug is present in all 1.3.x versions and 1.4.x versions and 1.5.x versions and 1.6.x versions.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
          /**
           * Paint one List cell: compute the relevant state, get the "rubber stamp"
           * cell renderer component, and then use the CellRendererPane to paint it.
           * Subclasses may want to override this method rather than paint().
           *
           * @see #paint
           */
          protected void paintCell(
              Graphics g,
              int row,
              Rectangle rowBounds,
              ListCellRenderer cellRenderer,
              ListModel dataModel,
              ListSelectionModel selModel,
              int leadIndex)
          {
              Object value = dataModel.getElementAt(row);
              boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
              boolean isSelected = selModel.isSelectedIndex(row);

              Component rendererComponent =
                  cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);

              int cx = rowBounds.x;
              int cy = rowBounds.y;
              int cw = rowBounds.width;
              int ch = rowBounds.height;

      if (isFileList) {
      // Shrink renderer to preferred size. This is mostly used on Windows
      // where selection is only shown around the file name, instead of
      // across the whole list cell.
      int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
      if (!isLeftToRight) {
      cx += (cw - w);
      }
      cw = w;
      }

              rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
         
       ///////////////// BUG ///////////// rendererComponent is never removed here
          }



          protected void updateLayoutState()
          {
              /* If both JList fixedCellWidth and fixedCellHeight have been
               * set, then initialize cellWidth and cellHeight, and set
               * cellHeights to null.
               */

              int fixedCellHeight = list.getFixedCellHeight();
              int fixedCellWidth = list.getFixedCellWidth();

              cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;

              if (fixedCellHeight != -1) {
                  cellHeight = fixedCellHeight;
                  cellHeights = null;
              }
              else {
                  cellHeight = -1;
                  cellHeights = new int[list.getModel().getSize()];
              }

              /* If either of JList fixedCellWidth and fixedCellHeight haven't
               * been set, then initialize cellWidth and cellHeights by
               * scanning through the entire model. Note: if the renderer is
               * null, we just set cellWidth and cellHeights[*] to zero,
               * if they're not set already.
               */

              if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {

                  ListModel dataModel = list.getModel();
                  int dataModelSize = dataModel.getSize();
                  ListCellRenderer renderer = list.getCellRenderer();

                  if (renderer != null) {
                      for(int index = 0; index < dataModelSize; index++) {
                          Object value = dataModel.getElementAt(index);
                          Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
                          rendererPane.add(c);
                          Dimension cellSize = c.getPreferredSize();
                          if (fixedCellWidth == -1) {
                              cellWidth = Math.max(cellSize.width, cellWidth);
                          }
                          if (fixedCellHeight == -1) {
                              cellHeights[index] = cellSize.height;
                          }

      /// BUG /// Component c which is the renderer component is added but never removed from rendererPane

                      }
                  }
                  else {
                      if (cellWidth == -1) {
                          cellWidth = 0;
                      }
                      if (cellHeights == null) {
                          cellHeights = new int[dataModelSize];
                      }
                      for(int index = 0; index < dataModelSize; index++) {
                          cellHeights[index] = 0;
                      }
                  }
              }

              columnCount = 1;
              if (layoutOrientation != JList.VERTICAL) {
                  updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
              }
          }



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



      ACTUAL -
      Every single instance of a renderer lingers in memory and is _never_ garbage collected. Eventually JVM runs out of memory.

      List UI has to be hacked into to remove the lingering instances of renderer to overcome this bug.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      I gave you the exact source code from java distribution.
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
          /**
           * Paint one List cell: compute the relevant state, get the "rubber stamp"
           * cell renderer component, and then use the CellRendererPane to paint it.
           * Subclasses may want to override this method rather than paint().
           *
           * @see #paint
           */
          protected void paintCell(
              Graphics g,
              int row,
              Rectangle rowBounds,
              ListCellRenderer cellRenderer,
              ListModel dataModel,
              ListSelectionModel selModel,
              int leadIndex)
          {
              Object value = dataModel.getElementAt(row);
              boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
              boolean isSelected = selModel.isSelectedIndex(row);

              Component rendererComponent =
                  cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);

              int cx = rowBounds.x;
              int cy = rowBounds.y;
              int cw = rowBounds.width;
              int ch = rowBounds.height;

      if (isFileList) {
      // Shrink renderer to preferred size. This is mostly used on Windows
      // where selection is only shown around the file name, instead of
      // across the whole list cell.
      int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
      if (!isLeftToRight) {
      cx += (cw - w);
      }
      cw = w;
      }

              rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
         
             // BUG FIX
            rendererPane.remove(rendererComponent);
          }



          protected void updateLayoutState()
          {
              /* If both JList fixedCellWidth and fixedCellHeight have been
               * set, then initialize cellWidth and cellHeight, and set
               * cellHeights to null.
               */

              int fixedCellHeight = list.getFixedCellHeight();
              int fixedCellWidth = list.getFixedCellWidth();

              cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;

              if (fixedCellHeight != -1) {
                  cellHeight = fixedCellHeight;
                  cellHeights = null;
              }
              else {
                  cellHeight = -1;
                  cellHeights = new int[list.getModel().getSize()];
              }

              /* If either of JList fixedCellWidth and fixedCellHeight haven't
               * been set, then initialize cellWidth and cellHeights by
               * scanning through the entire model. Note: if the renderer is
               * null, we just set cellWidth and cellHeights[*] to zero,
               * if they're not set already.
               */

              if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {

                  ListModel dataModel = list.getModel();
                  int dataModelSize = dataModel.getSize();
                  ListCellRenderer renderer = list.getCellRenderer();

                  if (renderer != null) {
                      for(int index = 0; index < dataModelSize; index++) {
                          Object value = dataModel.getElementAt(index);
                          Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
                          rendererPane.add(c);
                          Dimension cellSize = c.getPreferredSize();
                          if (fixedCellWidth == -1) {
                              cellWidth = Math.max(cellSize.width, cellWidth);
                          }
                          if (fixedCellHeight == -1) {
                              cellHeights[index] = cellSize.height;
                          }

      /// BUG FIX //
                         rendererPane.remove(c);

                      }
                  }
                  else {
                      if (cellWidth == -1) {
                          cellWidth = 0;
                      }
                      if (cellHeights == null) {
                          cellHeights = new int[dataModelSize];
                      }
                      for(int index = 0; index < dataModelSize; index++) {
                          cellHeights[index] = 0;
                      }
                  }
              }

              columnCount = 1;
              if (layoutOrientation != JList.VERTICAL) {
                  updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
              }
          }

            shickeysunw Shannon Hickey (Inactive)
            ndcosta Nelson Dcosta (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: