The Weakness of Swing's memory model

Swing's memory model was the protagonist of a bug that made in fact unusable an application developed with the NetBeans Platform. 

The scenario was the same of all memory leaks, the application that slows down with the Java Heap that grows fast, but we was unable to come to a solution because a static analisys of our code confirmed that we have no loss or circular references that can produce such problems.

At this point we have used a profiler and discovered that all the memory was "held" by a components of a graphics library to draw Gantt. In practice, even after this component was removed from our container (a TopComponent of NetBeans), a variety of data structures of Swing made it still strongly reachable by the GC and then "Not Eligible" for Garbage Collection. These include:

In this case make our listeners "Weak References" was not enough to keep clean our application.

We have thus achieved a minimal test case to try to restrict the full problem (and also to make sure that the NetBeans Platform does not enter into play ), consisting of a JFrame containing a DualGanttChart and a button that re-creates the whole. The interesting part of the code is:

        final JFrame frame = new JFrame();

        JButton button = new JButton("Reload");

        button.addActionListener(new WeakRefActionListener(new ActionListener() {

                  public void actionPerformed(ActionEvent e) {

                        frame.setVisible(false);

                        SwingUtilities.invokeLater(LayerDemo.this);

                  }

        }));

       

        frame.add(BorderLayout.NORTH, button);

        frame.setSize(640, 480);

        frame.setTitle("GANTT Chart Demo");

       

        frame.add(BorderLayout.CENTER, gc);

       

        setFrame(frame);

        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        frame.setVisible(true);

This application shows a Gantt, and when you click the button "Reload" closes the JFrame and creates another (this code is in the run() method ). This simple application can perfectly reproduce the problem of the memory leak, eating a few megabytes at every reload.

Unfortunately, the data structures mentioned above are all internal components for Swing, not accessible to the application code, and therefore managed by the framework, which is designed to not consider a memory leak retention of objects that can be overwritten by future components.

Let me explain better. 

Suppose you have a JFrame that contains a panel, at some point for some reasons we make the JFrame invisible, removing the panel because it consumes a lot of memory. Swing in this case, regardless of our decision whether or not to remove content, maintains a reference to the panel in the"temporaryLostComponent" which will not be overwritten until there will be a new Component into the JFrame.

And this is just one of the retentions, the list above shows others, like the worst KeyboardFocusManager.

What solution to adopt if you can not manually reset the reference? There are those who wrote the code to use reflection and writing regardless of visibility, but before arriving at a so drastic solutions we wanted to be sure there were no other "official" ways..... and fortunately Sun hasn't left us to admire inert Swing that retains our references, but has written a method in "KeyBoardFocusManager" that do exactly what we want, the "clearGlobalFocusOwner".

We don't know how this method can do it (finally calls a native method of a peer object), but does his work and the retention disappears completely! How we found it? Thanks to its name, as we wanted to clear "*focusOwners" into "KeyBoardFocusManager" and the name "clearGlobalFocusOwner" was an excellent candidate.

So to avoid memory leak we refactored the code above to include this call:

        ...........

        ...........

        ...........

        button.addActionListener(new WeakRefActionListener(new ActionListener() {

                  public void actionPerformed(ActionEvent e) {

                        frame.setVisible(false);

                        KeyboardFocusManager.

                                getCurrentKeyboardFocusManager().clearGlobalFocusOwner();

                        SwingUtilities.invokeLater(LayerDemo.this);

                  }

        }));

        ...........

        ...........

        ...........

Running the profiler again, we verified that in fact all Swing's internal references was cleared and the problem actually solved.