3

I am observing some inconsistent behaviour between OS and Java versions when calling JDialog.dispose to dispose a JDialog (also occurs for JFrame).

The simple sample application, below, can be used to demonstrate the problem. If you run it and profile the application you will notice that any JDialog instances created by clicking on the "New Dialog" and subsequently closed do not get garbage collected as they are still being referenced by instances of sun.lwawt.macosx.CPlatformWindow, causing a memory leak in the application.

I don't believe this is due to any weak references either as I observed this problem in an environment that had experienced an OutOfMemoryError, so I would expect that anything that could have been garbage collected would have been at that point.

The problem occurs in the following environments:

  • Mac OS X 10.9: Java 1.7.0_5
  • Mac OS X 10.9: Java 1.7.0_45

The problem does not occur in the following environments:

  • Mac OS X 10.9: Java 1.6.0_65
  • Windows 7: Java 1.7.0_45

In these environments the JDialog instances are promptly collected and (obviously) no longer visible in JProfiler.

Note: The problem occurs using DISPOSE_ON_CLOSE or handling the close manually as commented out in the sample.

import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.*;

public class Testing extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JDialog parent = new JDialog((Frame)null, "Parent", false);

                JButton add = new JButton("New Dialog");
                add.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        final JDialog child = new JDialog(parent, "Child", false);
                        // child.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
                        child.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);                       
                        child.setSize(100, 100);

                        //child.addWindowListener(new WindowAdapter() {
                        //    @Override
                        //    public void windowClosing(WindowEvent e) {
                        //        child.setVisible(false);
                        //        child.dispose();
                        //    }
                        //});
                        child.setVisible(true);
                    }
                });

                parent.add(add);
                parent.pack();
                parent.setVisible(true);
            }
        });
    }
}

Is there something that I am doing incorrectly?

Is my expected behaviour incorrect?

If not, can anyone point me to a Java bug report that covers this (I have had no luck finding one)?

Any suggested workarounds?

cmatthews.dickson
  • 687
  • 2
  • 8
  • 17
  • A related example is examined [here](http://stackoverflow.com/a/6310284/230513). – trashgod Nov 05 '13 at 17:34
  • @trashgod Thanks, I did see that during my investigations. However, I didn't see how it applied directly here, since in previous Java versions and different OSs, I am seeing different behaviours given the exact same code. – cmatthews.dickson Nov 05 '13 at 20:32
  • Could be latency or a bug, but see also this [answer](http://stackoverflow.com/a/2486200/230513). – trashgod Nov 05 '13 at 21:12
  • 3
    This defect has been logged at https://bugs.openjdk.java.net/browse/JDK-8029147 – cmatthews.dickson Dec 02 '13 at 14:11
  • Has this bug been fixed in recent JDKs? This is so old and so many versions have been released since. The openjdk bug tracker shows it as still open... after almost 8 years. – Jason Aug 18 '21 at 15:47

2 Answers2

2

I was seeing the same thing and was able to get it to release the window by overriding the dispose method on my window like this:

@SuppressWarnings("deprecation")
@Override
public void dispose()
{
    ComponentPeer peer = getPeer();

    super.dispose();

    if (null != peer)
    {
        try
        {
            Class<?> peerClass = Class.forName("sun.lwawt.LWComponentPeer");

            Field targetField = peerClass.getDeclaredField("target");
            targetField.setAccessible(true);
            targetField.set(peer, null);

            Field windowField = peer.getClass().getDeclaredField("platformWindow");
            windowField.setAccessible(true);
            Object platformWindow = windowField.get(peer);

            targetField = platformWindow.getClass().getDeclaredField("target");
            targetField.setAccessible(true);
            targetField.set(platformWindow, null);

            Field componentField = peerClass.getDeclaredField("platformComponent");
            componentField.setAccessible(true);
            Object platformComponent = componentField.get(peer);

            targetField = platformComponent.getClass().getDeclaredField("target");
            targetField.setAccessible(true);
            targetField.set(platformComponent, null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

This didn't release the CPlatformWindow but it is better than nothing and should help you.

Jayfray
  • 415
  • 3
  • 12
0

I use the following code to try and minimize the memory leak. There will still be resources uncollected by the garbage collector, but all the Swing components that were children of the JFrame or JDialog will be garbage collected. A shorter title (or no title) can be used to make the footprint even smaller. I kept a meaningful title so that I can more easily track things in the profiler if necessary. The memory footprint of my application with this code is plenty small for long runs and lots of window open and close operations. Without it, memory would run out with a few dozen open and close operations on certain heavy weight windows that some users were using while leaving the application open for days on end.

        protected void disposeAndEmptyOnClose(Component c) {
            if ( c instanceof JFrame ) {
                JFrame frame = (JFrame) c;
                if (!frame.getClass().isAssignableFrom(JFrame.class)) {
                    LOG.warn("potential memory leak. Cannot guarantee memory is freed after frame is disposed because" +
                        " JFrame has been subclassed to " + frame.getClass().getName());
                }
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosed(WindowEvent e) {
                        frame.removeAll();
                        frame.setContentPane(new JPanel());
                        frame.setJMenuBar(null);
                        frame.removeWindowListener(this);
                        frame.setTitle("disposed and emptied: "+frame.getTitle());
                    }
                });
            } else if ( c instanceof JDialog ) {
                JDialog dialog = (JDialog)c;
                if (!dialog.getClass().isAssignableFrom(JDialog.class)) {
                    LOG.warn("potential memory leak. Cannot guarantee memory is freed after dialog is disposed " +
                        "because JDialog has been subclassed to " + dialog.getClass().getName());
                }
                dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
                dialog.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosed(WindowEvent e) {
                        dialog.removeAll();
                        dialog.setContentPane(new JPanel());
                        dialog.removeWindowListener(this);
                        dialog.setTitle("disposed and emptied: "+dialog.getTitle());
                    }
                });
            } else {
                LOG.warn("disposeAndEmptyOnClose not supported for " + c.getClass().getSimpleName());
            }
        }
Jason
  • 11,709
  • 9
  • 66
  • 82