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

Memory leak in JTree - removed nodes kept in memory

    XMLWordPrintable

    Details

    • Subcomponent:
    • Understanding:
      Cause Known
    • CPU:
      x86
    • OS:
      linux_redhat_9.0

      Description

      There is a leak of TreePaths and their associated object in very specific (but not uncommon) scenario. The necessary conditions are:
      1. 2 or more nodes to be removed
      2. They have children
      3. They are expanded and both the nodes and their children are selected.
      If the model signals (single) removal event, all but one of the nodes will leak through the expandedState map.

      Here is the mechanism:
      1. remove event
       2. the removed nodes (and their descendants) are removed from the expandedState map.
       3. the removed nodes are removed from selection one by one
        4. as the first one is removed, selection change is fired with new selection covering
           the second node and its children.
         5. during selecting the children, ensureVsible is called, which also "expands" parent
            node (the second original node, already removed) by placing it back to the expandedState
            map -> the leak

      This happens e.g. if you expand few projects in NetBeans, select them using keyboard (with their subnodes) and close them using file->close %d projects.
      I'll attach simple test case showin the leak on pure JTree+DefaultModel.
      I am adding the test case to the Description field at the request of the jdk.dev.java.net community:

      /*
       * Tree.java
       *
       * Created on 19. záÅí 2006, 15:55
       */

      package treenodeleak;

      import javax.swing.tree.DefaultMutableTreeNode;
      import javax.swing.tree.DefaultTreeModel;
      import javax.swing.tree.MutableTreeNode;
      import javax.swing.tree.TreeModel;

      /**
       
       @author nenik
       */
      public class Tree extends javax.swing.JFrame {
          DefaultTreeModel model;
          DefaultMutableTreeNode root;
          DefaultMutableTreeNode[] level1;
          
          /** Creates new form Tree */
          public Tree() {
              initComponents();
              root = new DefaultMutableTreeNode("Root");
              level1 = new DefaultMutableTreeNode[] {
                  new DefaultMutableTreeNode("ch1", true),
                  new DefaultMutableTreeNode("ch2", true),
                  new DefaultMutableTreeNode("ch3", true)
              };
              
              for (int i=0; i<level1.length; i++) {
                  level1[i].add(new DefaultMutableTreeNode("A", false));
                  level1[i].add(new DefaultMutableTreeNode("B", false));
                  root.add(level1[i]);
              }
              
              model = new DefaultTreeModel(root, true);
              tree.setModel(model);
              tree.expandRow(3);
              tree.expandRow(2);
              tree.expandRow(1);
              tree.setSelectionInterval(1, 6);
          }
          
          /** This method is called from within the constructor to
           initialize the form.
           WARNING: Do NOT modify this code. The content of this method is
           always regenerated by the Form Editor.
           */
          // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
          private void initComponents() {
              scroll = new javax.swing.JScrollPane();
              tree = new javax.swing.JTree();
              button = new javax.swing.JButton();

              setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
              scroll.setViewportView(tree);

              getContentPane().add(scroll, java.awt.BorderLayout.CENTER);

              button.setText("Remove Nodes");
              button.addActionListener(new java.awt.event.ActionListener() {
                  public void actionPerformed(java.awt.event.ActionEvent evt) {
                      buttonActionPerformed(evt);
                  }
              });

              getContentPane().add(button, java.awt.BorderLayout.SOUTH);

              pack();
          }// </editor-fold>//GEN-END:initComponents

          private void buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonActionPerformed
              int[] childIndex = new int[] {0, 1};
              Object[] removedArray = new Object[] { level1[0], level1[1] };

              root.remove(0);
              root.remove(1);
              model.nodesWereRemoved(root, childIndex, removedArray);
              button.setEnabled(false);
              level1 = null;
          }//GEN-LAST:event_buttonActionPerformed
          
          /**
           @param args the command line arguments
           */
          public static void main(String args[]) {
              java.awt.EventQueue.invokeLater(new Runnable() {
                  public void run() {
                      new Tree().setVisible(true);
                  }
              });
          }
          
          // Variables declaration - do not modify//GEN-BEGIN:variables
          javax.swing.JButton button;
          javax.swing.JScrollPane scroll;
          javax.swing.JTree tree;
          // End of variables declaration//GEN-END:variables
          
      }

        Attachments

          Activity

            People

            Assignee:
            Unassigned Unassigned
            Reporter:
            duke J. Duke (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Dates

              Created:
              Updated:
              Imported:
              Indexed: