|
|
|
The following snippet demonstrates the problem. Open Dialog, close it, trigger roundtrip few times, the model is finalized but not the adapter.
import com.ulcjava.base.application.AbstractApplication; import com.ulcjava.base.application.ULCBorderLayoutPane; import com.ulcjava.base.application.ULCBoxPane; import com.ulcjava.base.application.ULCButton; import com.ulcjava.base.application.ULCDialog; import com.ulcjava.base.application.ULCFrame; import com.ulcjava.base.application.ULCScrollPane; import com.ulcjava.base.application.ULCTableTree; import com.ulcjava.base.application.event.ActionEvent; import com.ulcjava.base.application.event.IActionListener; import com.ulcjava.base.application.tabletree.DefaultMutableTableTreeNode; import com.ulcjava.base.application.tabletree.DefaultTableTreeCellRenderer; import com.ulcjava.base.application.tabletree.DefaultTableTreeModel; import com.ulcjava.base.application.tabletree.ITableTreeModel; import com.ulcjava.base.application.tabletree.ULCTableTreeColumn; import com.ulcjava.base.development.DevelopmentRunner; import com.ulcjava.base.server.IModelAdapterProvider; import com.ulcjava.base.server.ULCSession; import com.ulcjava.base.server.ULCTableTreeModelAdapter; public class PR7363 extends AbstractApplication { public void start() { final ULCFrame frame = new ULCFrame("Snippet"); ULCBoxPane boxPane = new ULCBoxPane(false); ULCButton button = new ULCButton("Show Dialog"); button.addActionListener(new IActionListener() { public void actionPerformed(ActionEvent event) { createDialog(frame).setVisible(true); } }); boxPane.add(button); button = new ULCButton("Trigger Roundtrip"); button.addActionListener(new IActionListener() { public void actionPerformed(ActionEvent event) { Runtime.getRuntime().gc(); } }); boxPane.add(button); frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE); frame.add(boxPane); frame.setVisible(true); } private ULCDialog createDialog(ULCFrame frame) { IModelAdapterProvider provider = ULCSession.getCurrentSessionRegistry().getCurrentSession().getModelAdapterProvider(); SnipetTableTreeModel snipetTableTreeModel = new SnipetTableTreeModel(); provider.registerModelAdapter(ITableTreeModel.class, snipetTableTreeModel, new MyTableTreeModelAdapter(snipetTableTreeModel)); ULCDialog dialog = new ULCDialog(null, "Dialog"); dialog.setDefaultCloseOperation(ULCDialog.DISPOSE_ON_CLOSE); ULCTableTree tableTree = new ULCTableTree(snipetTableTreeModel); DefaultTableTreeCellRenderer renderer = new DefaultTableTreeCellRenderer(); renderer.setToolTipText("snippet tool tip text"); ULCTableTreeColumn treeColumn = tableTree.getColumnModel().getColumn(0); treeColumn.setCellRenderer(renderer); treeColumn = tableTree.getColumnModel().getColumn(1); treeColumn.setCellRenderer(renderer); treeColumn = tableTree.getColumnModel().getColumn(2); treeColumn.setCellRenderer(renderer); dialog.getContentPane().add(new ULCScrollPane(tableTree), ULCBorderLayoutPane.CENTER); return dialog; } public static void main(String[] args) { DevelopmentRunner.setApplicationClass(PR7363.class); DevelopmentRunner.main(args); } public static class SnipetTableTreeModel extends DefaultTableTreeModel { public SnipetTableTreeModel() { super(createRoot(), createColumnNames(3)); } protected void finalize() throws Throwable { super.finalize(); System.out.println("Model Finalized"); } private static DefaultMutableTableTreeNode createRoot() { DefaultMutableTableTreeNode child0 = createNode("root:0", 3); child0.add(createNode("root:0:0", 3)); child0.add(createNode("root:0:1", 3)); DefaultMutableTableTreeNode child1 = createNode("root:1", 3); child1.add(createNode("root:1:0", 3)); child1.add(createNode("root:1:1", 3)); child1.add(createNode("root:1:2", 3)); DefaultMutableTableTreeNode child2 = createNode("root:2", 3); child2.add(createNode("root:2:0", 3)); DefaultMutableTableTreeNode result = createNode("root", 3); result.add(child0); result.add(child1); result.add(child2); return result; } private static String[] createColumnNames(int columns) { String[] result = new String[columns]; for (int i = 0; i < result.length; i++) { result[i] = "column " + i; } return result; } private static DefaultMutableTableTreeNode createNode(String prefix, int columns) { Object[] data = new Object[columns]; for (int i = 0; i < data.length; i++) { data[i] = prefix + ":" + i; } return new DefaultMutableTableTreeNode(data); } } public static class MyTableTreeModelAdapter extends ULCTableTreeModelAdapter { public MyTableTreeModelAdapter(ITableTreeModel model) { super(model); } protected void finalize() throws Throwable { super.finalize(); System.out.println("ModelAdapter Finalized"); } } } The same is true for the list of frames (ULCFrame.getFrames()) and the list of owned windows (ULCWindow.getOwnedWindows()).
Fixed these two places as well. | ||||||||||||||||||||||||||||||||||||||||||||||
The WeakHashMap holds the values as hard references. So the values can only be collected after they have been removed from the WeakHashMap and they are only removed after someone accesses the WeakHashMap. If no one ever accesses the WeakHashMap again then the values can never be garbage collected => a memory leak.
The idea here is to regularly access the WeakHashMap so the WeakHashMap is able to free the values. One possibility is to do a dummy access WeakHashMap in each round trip.