History | Log In     View a printable version of the current page.  

Issue Details (XML | Word | Printable)

Key: UBA-7363
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Janak Mulani
Reporter: Marcel Rüedi
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
ULCBase

MemoryLeak in DefaultModelAdapterProvider

Created: 21/Dec/07 04:08 PM   Updated: 02/Apr/08 11:25 PM
Component/s: core
Affects Version/s: None
Fix Version/s: UltraLightClient '08

File Attachments: 1. Java Source File TableTreeMemLeak.java (4 kb)



 Description  « Hide
The management of Modeladapters of DefaultModelAdapterProvider uses a weakHashMap for each model class. If no more models of a class are created , the weakHashMap gets never accessed again and therefore does not release the last modeladapters.

 All   Comments   Change History      Sort Order:
Daniel Grob - [17/Jan/08 12:02 PM ]
A WeakHashMap keeps the keys as weak references. The WeakHashMap manages a list with garbage collected keys. Whenever the garbage collector collects a key then the garbage collector adds the key to this list. Whenever someone accesses the WeakHashMap then the WeakHashMap first goes through the list with collected keys and removes the corresponding entry (the key / value pair) from the WeakHashMap.

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.


Janak Mulani - [17/Mar/08 06:05 PM ]
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");
        }
    }
}

Daniel Grob - [02/Apr/08 11:25 PM ]
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.