-
Bug
-
Resolution: Fixed
-
P4
-
5.0
-
b10
-
x86
-
windows_xp
FULL PRODUCT VERSION :
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Client VM (build 1.5.0_08-b03, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When using a TreeCellRenterer which creates new components in getTreeCellRendererComponent() in a JTree that is not visible, changes to the nodes cause a memory leak.
BasicTreeUI.java is the problem. When a node is changed, the Method getNodeDimensions() is called to calculate the new dimensions for the node. In this method, getTreeCellRendererComponent() is called to obtain the renderer component (what else...) and this component is added to rendererPane. It is not removed from the rendererPane afterwards. Only when the tree is painted, the paint() method does a removeAll on the rendererPane.
If the tree is not visible (for example, in an inactive tab of a JTabbedPane), the paint() method is never called. The rendererPane fills up with more and more renderer components which are never GCed.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a JTabbedPane with a JTree in one tab.
For the JTree, set a custom TreeCellRenderer which always creates a new JLabel in its getTreeCellRendererComponent() method.
Run a Thread which fires nodeChanged() on one of the nodes.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No memory leaks.
ACTUAL -
When the tab with the JTree is not active, more and more JLabels are created in the getTreeCellRendererComponent() method and never GCed.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package treeuibug;
import java.awt.Component;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
public class TestFrame extends javax.swing.JFrame {
static long smCount = 0;
// JLabel with an instance counter
public class TestLabel extends JLabel {
public TestLabel() {
smCount++;
}
public void finalize( ) {
smCount--;
}
}
// Custom TreeCellRenderer
public class TreeCellRenderer extends DefaultTreeCellRenderer {
public TreeCellRenderer( ) {
}
// Create a new JLabel every time
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
JLabel label = new TestLabel( );
label.setText("TreeNode: " + value.toString());
if (sel) {
label.setBackground(getBackgroundSelectionColor());
} else {
label.setBackground(getBackgroundNonSelectionColor());
}
return label;
}
}
public TestFrame() {
initComponents();
jTree1.setCellRenderer(new TreeCellRenderer());
Thread updateThread = new Thread(new Runnable( ) {
public void run( ) {
runChanges( );
}
});
updateThread.setDaemon(true);
updateThread.start();
Thread infoThread = new Thread(new Runnable( ) {
public void run( ) {
runInfo( );
}
});
infoThread.setDaemon(true);
infoThread.start();
}
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">
private void initComponents() {
jTabbedPane1 = new javax.swing.JTabbedPane();
jPanel1 = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
jTree1 = new javax.swing.JTree();
jPanel2 = new javax.swing.JPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setLayout(new java.awt.BorderLayout());
jScrollPane1.setViewportView(jTree1);
jPanel1.add(jScrollPane1, java.awt.BorderLayout.CENTER);
jTabbedPane1.addTab("tab1", jPanel1);
jPanel2.setLayout(new java.awt.BorderLayout());
jTabbedPane1.addTab("tab2", jPanel2);
getContentPane().add(jTabbedPane1, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
TestFrame tf = new TestFrame();
tf.setVisible(true);
}
});
}
// Periodically cause a nodeChanged() for one of the nodes
public void runChanges() {
long count = 0;
while (true) {
final long currentCount = count;
try {
SwingUtilities.invokeAndWait(new Runnable( ) {
public void run( ) {
DefaultTreeModel model = (DefaultTreeModel) jTree1.getModel();
TreeNode root = (TreeNode) model.getRoot();
DefaultMutableTreeNode n = (DefaultMutableTreeNode) model.getChild(root, 0);
n.setUserObject("runcount " + currentCount);
model.nodeChanged(n);
}
});
count++;
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
// Print number of uncollected TestLabels
public void runInfo( ) {
while (true) {
System.gc();
System.err.println("Live TestLabels:" + smCount);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
// Variables declaration - do not modify
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTabbedPane jTabbedPane1;
private javax.swing.JTree jTree1;
// End of variables declaration
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use a caching mechanism to reuse the renderer components. This limits the number of renderer components created to the number of nodes in the tree.
For some appliations this may not be feasable however.
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Client VM (build 1.5.0_08-b03, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
When using a TreeCellRenterer which creates new components in getTreeCellRendererComponent() in a JTree that is not visible, changes to the nodes cause a memory leak.
BasicTreeUI.java is the problem. When a node is changed, the Method getNodeDimensions() is called to calculate the new dimensions for the node. In this method, getTreeCellRendererComponent() is called to obtain the renderer component (what else...) and this component is added to rendererPane. It is not removed from the rendererPane afterwards. Only when the tree is painted, the paint() method does a removeAll on the rendererPane.
If the tree is not visible (for example, in an inactive tab of a JTabbedPane), the paint() method is never called. The rendererPane fills up with more and more renderer components which are never GCed.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a JTabbedPane with a JTree in one tab.
For the JTree, set a custom TreeCellRenderer which always creates a new JLabel in its getTreeCellRendererComponent() method.
Run a Thread which fires nodeChanged() on one of the nodes.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No memory leaks.
ACTUAL -
When the tab with the JTree is not active, more and more JLabels are created in the getTreeCellRendererComponent() method and never GCed.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package treeuibug;
import java.awt.Component;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
public class TestFrame extends javax.swing.JFrame {
static long smCount = 0;
// JLabel with an instance counter
public class TestLabel extends JLabel {
public TestLabel() {
smCount++;
}
public void finalize( ) {
smCount--;
}
}
// Custom TreeCellRenderer
public class TreeCellRenderer extends DefaultTreeCellRenderer {
public TreeCellRenderer( ) {
}
// Create a new JLabel every time
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
JLabel label = new TestLabel( );
label.setText("TreeNode: " + value.toString());
if (sel) {
label.setBackground(getBackgroundSelectionColor());
} else {
label.setBackground(getBackgroundNonSelectionColor());
}
return label;
}
}
public TestFrame() {
initComponents();
jTree1.setCellRenderer(new TreeCellRenderer());
Thread updateThread = new Thread(new Runnable( ) {
public void run( ) {
runChanges( );
}
});
updateThread.setDaemon(true);
updateThread.start();
Thread infoThread = new Thread(new Runnable( ) {
public void run( ) {
runInfo( );
}
});
infoThread.setDaemon(true);
infoThread.start();
}
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">
private void initComponents() {
jTabbedPane1 = new javax.swing.JTabbedPane();
jPanel1 = new javax.swing.JPanel();
jScrollPane1 = new javax.swing.JScrollPane();
jTree1 = new javax.swing.JTree();
jPanel2 = new javax.swing.JPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setLayout(new java.awt.BorderLayout());
jScrollPane1.setViewportView(jTree1);
jPanel1.add(jScrollPane1, java.awt.BorderLayout.CENTER);
jTabbedPane1.addTab("tab1", jPanel1);
jPanel2.setLayout(new java.awt.BorderLayout());
jTabbedPane1.addTab("tab2", jPanel2);
getContentPane().add(jTabbedPane1, java.awt.BorderLayout.CENTER);
pack();
}// </editor-fold>
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
TestFrame tf = new TestFrame();
tf.setVisible(true);
}
});
}
// Periodically cause a nodeChanged() for one of the nodes
public void runChanges() {
long count = 0;
while (true) {
final long currentCount = count;
try {
SwingUtilities.invokeAndWait(new Runnable( ) {
public void run( ) {
DefaultTreeModel model = (DefaultTreeModel) jTree1.getModel();
TreeNode root = (TreeNode) model.getRoot();
DefaultMutableTreeNode n = (DefaultMutableTreeNode) model.getChild(root, 0);
n.setUserObject("runcount " + currentCount);
model.nodeChanged(n);
}
});
count++;
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
// Print number of uncollected TestLabels
public void runInfo( ) {
while (true) {
System.gc();
System.err.println("Live TestLabels:" + smCount);
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
// Variables declaration - do not modify
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTabbedPane jTabbedPane1;
private javax.swing.JTree jTree1;
// End of variables declaration
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use a caching mechanism to reuse the renderer components. This limits the number of renderer components created to the number of nodes in the tree.
For some appliations this may not be feasable however.