Java Swing JTree is not garbage collected
The following demo app creates a JFrame with a JInternalFrame (MDI interface). The internal frame features a JTree, with a JTree model. To simulate a large model, a 10MB buffer is associated to the JTree model. When the internal frame is closed (disposed), the JTree and its model will never be garbage collected.
jvisualvm shows the reason -- some static Swing class fields will keep references to the JTree. Unlike other Swing memory leaks no event handler is used here.
Is this a bug? Is there a clean solution collecting the disposed internal frame, its tree and its model (besides workarounds like using weak references, nullifying the data in the JTree model)?
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
public class Test extends javax.swing.JFrame {
public Test() {
javax.swing.JDesktopPane jDesktopPane = new javax.swing.JDesktopPane();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setContentPane(jDesktopPane);
InternalTreeFrame f = new InternalTreeFrame();
jDesktopPane.add(f);
f.show开发者_如何转开发();
pack();
setBounds(10, 10, 400, 300);
}
public class InternalTreeFrame extends javax.swing.JInternalFrame {
public InternalTreeFrame() {
javax.swing.JScrollPane jScrollPane = new javax.swing.JScrollPane();
javax.swing.JTree jTree = new javax.swing.JTree();
jScrollPane.setViewportView(jTree);
jTree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(new LargeObject("big root"))));
setContentPane(jScrollPane);
setClosable(true);
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
pack();
setBounds(10, 10, 300, 200);
}
}
// 10MB helper object, easy to spot in visual vm heap dump
public class LargeObject {
public LargeObject(String name) {
this.name = name;
buff = new byte[1024*1024*10];
}
@Override
public String toString() {
return name;
}
private final String name;
private final byte[] buff;
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Test().setVisible(true);
}
});
}
}
Not sure if this is a bug, but a solution would be to set an empty model into the JTree in the windowClosing() event of the (internal) frame
There is a known bug in the JDK about the last JInternalFrame not being garbage collected correctly on dispose
. This is because the JDesktopPane
maintains a framesCache
which holds references to the JInternalFrames. This cache is not refreshed when the last JInternalFrame is closed.
The workaround is to force the cache to reload by calling JDesktopPane.selectFrame
, as shown below:
f.addInternalFrameListener(new InternalFrameAdapter() {
public void internalFrameClosed(InternalFrameEvent e) {
jDesktopPane.selectFrame(true);
}
});
Try it out and you will see memory reclaimed once the JInternalFrame is closed.
精彩评论