Remove Top-Level Container on Runtime
Invoking dispose()
allows the host platform to reclaim memory consumed by the heavyweight peer, but it can't do so until after the WINDOW_CLOSING
event is processed on the EventQueue
. Even then, gc()
is a suggestion.
Addendum: Another way to see the nightmare is via a profiler. Running the example below with jvisualvm
, one can see that periodic collection never quite returns to baseline. I've exaggerated the vertical axis by starting with an artificially small heap. Additional examples are shown here. When memory is very limited, I've used two approaches:
Emergent: Loop from the command line, starting a new VM each time.
Urgent: Eliminate the heavyweight component entirely, running headless and composing in a
BufferedImage
using 2D graphics and lightweight components only.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.WindowEvent;
import javax.swing.JDialog;
/** @see https://stackoverflow.com/questions/6309407 */
public class DialogClose extends JDialog {
public DialogClose(int i) {
this.setTitle("Dialog " + String.valueOf(i));
this.setPreferredSize(new Dimension(320, 200));
}
private void display() {
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
passSomeTime();
this.setVisible(false);
this.dispatchEvent(new WindowEvent(
this, WindowEvent.WINDOW_CLOSING));
this.dispose();
passSomeTime();
}
private void passSomeTime() {
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace(System.err);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
int count = 0;
while (true) {
new DialogClose(count++).display();
}
}
});
}
}
I have completely reworked your example:
- I have simplified what was not needed (
setLocation()
, unused constructor...) - I have removed the code that triggers a WINDOW_CLOSING event (useless)
- I have removed code that resets all windows to visible again (which would prevent GC on them)
- I have used a
javax.swing.Timer
instead of aThread
for disposing of the dialog - I have used a
Thread
for forcing GC (not a good idea in the EDT) - I have modified the final success criterion to check that
Window.getWindows()
is 2 (not 1), because in Swing, if you open a dialog with no parent, then a special invisible frame will be created to use it as parent (for all ownerless dialogs actually), once created, that frame cannot be removed.
The resulting snippet follows:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class RemoveDialogOnRuntime extends JFrame {
private static final long serialVersionUID = 1L;
private boolean runProcess;
private int maxLoop = 0;
private Timer timer;
public RemoveDialogOnRuntime() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 300));
setTitle("Remove Dialog On Runtime");
setLocation(150, 150);
pack();
setVisible(true);
addNewDialog();
}
private void addNewDialog() {
DialogRemove firstDialog = new DialogRemove();
remWins();
}
private void remWins() {
runProcess = true;
timer = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (runProcess) {
for (Window win: Window.getWindows()) {
if (win instanceof JDialog) {
System.out.println(" Trying to Remove JDialog");
win.dispose();
}
}
System.out.println(" Remove Cycle Done :-)");
runProcess = false;
new Thread() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runtime.getRuntime().gc();
}
}.start();
} else {
pastRemWins();
runProcess = true;
}
}
});
timer.setRepeats(true);
timer.start();
}
private void pastRemWins() {
System.out.println(" Checking if still exists any of TopLayoutContainers");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JFrame) {
System.out.println("JFrame");
} else if (wins[i] instanceof JDialog) {
System.out.println("JDialog");
} else {
System.out.println(wins[i].getClass().getSimpleName());
}
}
// We must expect 2 windows here: this (RemoveDialogOnRuntime) and the parent of all parentless dialogs
if (wins.length > 2) {
wins = null;
maxLoop++;
if (maxLoop <= 3) {
System.out.println(" Will Try Remove Dialog again, CycleNo. " + maxLoop);
System.out.println(" -----------------------------------------------------------");
remWins();
} else {
System.out.println(" -----------------------------------------------------------");
System.out.println("*** End of Cycle Without Success, Exit App ***");
closeMe();
}
} else {
timer.stop();
}
}
private void closeMe() {
System.exit(0);
}
private class DialogRemove extends JDialog {
private static final long serialVersionUID = 1L;
private DialogRemove() {
setTitle("SecondDialog");
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RemoveDialogOnRuntime superConstructor = new RemoveDialogOnRuntime();
}
});
}
}
The important conclusions are:
- You can't remove the invisible frame created by Swing as parent of all ownerless dialogs
- You have to force a GC for the disposed dialog to be removed from
Window.getWindows()
(that one looks like a bug to me but I think the reason is that Swing keeps aWeakReference
to all windows, and thisWeakReference
is not released until a GC has occurred.
Hope this gives a clear and complete answer to your problem.
with the intent to blow away all doubts about EDT and confirm trashgod Updated suggestion, then output to the console is
run:
7163 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
405 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 1
-----------------------------------------------------------
3274 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
403 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 2
-----------------------------------------------------------
3271 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
406 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
Will Try Remove Dialog again, CycleNo. 3
-----------------------------------------------------------
3275 KB used before GC
Trying to Remove JDialog
Remove Cycle Done :-)
403 KB used after GC
Checking if still exists any of TopLayoutContainers
JFrame
JDialog
-----------------------------------------------------------
*** End of Cycle Without Success, Exit App ***
BUILD SUCCESSFUL (total time: 26 seconds)
from code
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class RemoveDialogOnRuntime extends JFrame {
private static final long serialVersionUID = 1L;
private int contID = 1;
private boolean runProcess;
private int top = 20;
private int left = 20;
private int maxLoop = 0;
private javax.swing.Timer timer = null;
public RemoveDialogOnRuntime() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(300, 300));
setTitle("Remove Dialog On Runtime");
setLocation(150, 150);
pack();
setVisible(true);
Point loc = this.getLocation();
top += loc.x;
left += loc.y;
AddNewDialog();
}
private void AddNewDialog() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
DialogRemove firstDialog = new DialogRemove();
startAA();
}
});
}
private void startAA() {
timer = new javax.swing.Timer(5000, updateAA());
timer.setRepeats(false);
timer.start();
}
public Action updateAA() {
return new AbstractAction("text load action") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
if (SwingUtilities.isEventDispatchThread()) {
Runnable doRun = new Runnable() {
@Override
public void run() {
remWins();
}
};
SwingUtilities.invokeLater(doRun);
} else {
Runnable doRun = new Runnable() {
@Override
public void run() {
remWins();
}
};
SwingUtilities.invokeLater(doRun);
}
}
};
}
private void remWins() {
Runtime runtime = Runtime.getRuntime();
long total = runtime.totalMemory();
long free = runtime.freeMemory();
long max = runtime.maxMemory();
long used = total - free;
System.out.println(Math.round(used / 1e3) + " KB used before GC");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JDialog) {
System.out.println(" Trying to Remove JDialog");
wins[i].setVisible(false);
wins[i].dispose();
WindowEvent windowClosing = new WindowEvent(wins[i], WindowEvent.WINDOW_CLOSING);
wins[i].dispatchEvent(windowClosing);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(windowClosing);
runtime = Runtime.getRuntime();
runtime.gc();
runtime.runFinalization();
}
}
wins = null;
System.out.println(" Remove Cycle Done :-)");
runtime.runFinalization();
runtime.gc();
runtime = Runtime.getRuntime();
total = runtime.totalMemory();
free = runtime.freeMemory();
max = runtime.maxMemory();
used = total - free;
System.out.println(Math.round(used / 1e3) + " KB used after GC");
startOO();
}
private void startOO() {
timer = new javax.swing.Timer(5000, updateOO());
timer.setRepeats(false);
timer.start();
}
public Action updateOO() {
return new AbstractAction("text load action") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
timer.stop();
if (SwingUtilities.isEventDispatchThread()) {
Runnable doRun = new Runnable() {//really contraproductive just dealayed
@Override
public void run() {
pastRemWins();
}
};
SwingUtilities.invokeLater(doRun);
} else {
Runnable doRun = new Runnable() {
@Override
public void run() {
pastRemWins();
}
};
SwingUtilities.invokeLater(doRun);
}
}
};
}
private void pastRemWins() {
System.out.println(" Checking if still exists any of TopLayoutContainers");
Window[] wins = Window.getWindows();
for (int i = 0; i < wins.length; i++) {
if (wins[i] instanceof JFrame) {
System.out.println("JFrame");
wins[i].setVisible(true);
} else if (wins[i] instanceof JDialog) {
System.out.println("JDialog");
wins[i].setVisible(true);
}
}
if (wins.length > 1) {
wins = null;
maxLoop++;
if (maxLoop <= 3) {
System.out.println(" Will Try Remove Dialog again, CycleNo. " + maxLoop);
System.out.println(" -----------------------------------------------------------");
remWins();
} else {
System.out.println(" -----------------------------------------------------------");
System.out.println("*** End of Cycle Without Success, Exit App ***");
closeMe();
}
}
startAA();
}
private void closeMe() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
System.exit(0);
}
});
}
private class DialogRemove extends JDialog {
private static final long serialVersionUID = 1L;
DialogRemove(final Frame parent) {
super(parent, "SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
private DialogRemove() {
setTitle("SecondDialog " + (contID++));
setLocation(top, left);
top += 20;
left += 20;
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModalityType(Dialog.ModalityType.MODELESS);
pack();
setVisible(true);
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RemoveDialogOnRuntime superConstructor = new RemoveDialogOnRuntime();
}
});
}
}
I'm not sure if you question is about "garbage collection" or about how to identify dialogs that are visible.
You can't control when garbage collection is done. Invoking the gc() method is only a suggestion.
If you want to ignore "disposed" dialogs then you can use the isDisplayable() method to check its status.
With the following program I got some interesting results. First change I made was to add some components to the dialog so that more resources would be used for each dialog which would increase the chance that the resources would be garbage collected.
On my machine I found that if I
a) create 5 dialogs
b) close the dialogs
c) create 5 dialogs
Then the first 5 appear to be garbage collected.
However if I create 5, then close then create 1, then close, it doesn't seem to work.
Bottom line is you can't depend on when garbage collection will be done, so I suggest you use the isDisplayable() method to determine how to do your processing. The "Display Dialogs" button uses this method as part of the displayed output.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DialogSSCCE extends JPanel
{
public static int count;
public DialogSSCCE()
{
JButton display = new JButton("Display Dialogs");
display.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Display Dialogs");
for (Window window: Window.getWindows())
{
if (window instanceof JDialog)
{
JDialog dialog = (JDialog)window;
System.out.println("\t" + dialog.getTitle() + " " + dialog.isDisplayable());
}
}
}
});
add( display );
JButton open = new JButton("Create Dialog");
open.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Create Dialog");
JDialog dialog = new JDialog();
dialog.getContentPane().setLayout(null);
for (int i = 0; i < 200; i++)
{
dialog.add( new JTextField("some text") );
}
dialog.setTitle("Dialog " + count++);
dialog.setLocation(count * 25, count * 25);
dialog.setVisible(true);
System.out.println("\tCreated " + dialog.getTitle());
}
});
add( open );
JButton close = new JButton("Close Dialogs");
close.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println();
System.out.println("Close Dialogs");
for (Window window: Window.getWindows())
{
if (window instanceof JDialog)
{
JDialog dialog = (JDialog)window;
System.out.println("\tClosing " + dialog.getTitle());
dialog.dispose();
}
}
Runtime.getRuntime().gc();
}
});
add( close );
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("DialogSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new DialogSSCCE() );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}