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?
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.
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());
}
}
Related
What triggers the garbage collection of JFrames and other Swing components? In the following code, analyzing the heap during the second Thread.sleep() reveals that the JFrame instances have not been garbage collected (I'm using VisualVM in particular). Even after manually running the garbage collection in VisualVM the instance still exists. Is the Swing thread somehow holding a reference to these? Interestingly, if the first Thread.sleep(2000) is removed, the JFrame is collected.
import javax.swing.JFrame;
public class TestDemo {
public static void makeFrame() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setVisible(true);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
frame.dispose();
frame = null;
}
public static void main(String[] args) {
makeFrame();
System.gc();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Any ideas why this might be?
In my program I am testing a button which when clicked throws up a JOptionPane window in the application.
However when this window pops up it expects the user to click OK which means that the test can't continue unless the window is manually closed down.
I want to be able to close this window programmatically when it appears within my test or prevent these JOptionPane windows from popping up at all for this test.
What I typically do in such case is to make sure the UI does not appear in my test. Just to illustrate this with some code. Let's say the dialog is triggered somewhere in an Action
public class ActionWithUI extends AbstractAction{
#Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = JOptionPane.show... //show the dialog
//do some other stuff based on the result of the JOptionPane
}
}
I would then refactor my code so that the showing of the JOptionPane is in its own separate method.
public class ActionWithUI extends AbstractAction{
#Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = askUserForInput();
//do some other stuff based on the result of the JOptionPane
}
int askUserForInput(){
return JOptionPane.show...
}
}
Now in my test, I can test a custom version of that action where I override the askUserForInput method and just return the "OK" (or "Cancel", ... ) option. That will avoid any clever tricks in my test and keeps my code readable.
Another approach I sometimes use is not calling JOptionPane.show directly, but rather make sure my class accepts in its constructor a delegate class for showing such dialogs. In my test, I can then insert a mock instead of the real class, and intercept the call to the UI.
Of course, both of these approaches do not actually test whether the JOptionPane is correctly shown. But seeing as that is a JDK method, I do not really feel the need to test this. And it is not like I am bypassing some business logic. I just got rid of a JOptionPane.show call.
If none of these are an option, I normally use a listener attached to the DefaultKeyboardFocusManager. When the component which has the focus changes, I see whether it is the JOptionPane (using the Swing hierarchy) and dispose of it. This turns out to work quite well, but still not 100% reliable. Therefore (and certainly for new code), I try to stick to one of the two previously outlined approaches
There are two basic ways to overcome this issue. Both require you to be able to find the button.
This will require to be able to use a second Thread to find the window/dialog and walk it's container to find the button...
Depending on how your tests are set up will depend on how much additional work you might need to go through. The UI should be operating within the Event Dispatching Thread, if your tests are running in the same thread, then you will need to use separate Thread, as the JOptionPane will block the EDT (in a special way), making it impossible for your code to run until the option pane is closed.
If your tests are already running in a separate Thread, then you have a little more capability...
This is a very simple example. It initialises a JOptionPane within the context of the EDT, but has methods that are running in the main thread which find and execute the "OK" button of the message dialog.
import java.awt.Button;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Window;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FindButton {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JOptionPane.showMessageDialog(null, "This is message dialog", "Message", JOptionPane.INFORMATION_MESSAGE);
}
});
JDialog frame = waitForDialog("Message");
System.out.println("Found window " + frame);
if (frame != null) {
final JButton btn = getButton(frame, "OK");
System.out.println("Found button " + btn);
if (btn != null) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
btn.doClick();
}
});
}
}
}
public static JDialog waitForDialog(String title) {
JDialog win = null;
do {
for (Window window : Frame.getWindows()) {
if (window instanceof JDialog) {
JDialog dialog = (JDialog) window;
System.out.println(dialog.getTitle());
if (title.equals(dialog.getTitle())) {
win = dialog;
break;
}
}
}
if (win == null) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
break;
}
}
} while (win == null);
return win;
}
public static JButton getButton(Container container, String text) {
JButton btn = null;
List<Container> children = new ArrayList<Container>(25);
for (Component child : container.getComponents()) {
System.out.println(child);
if (child instanceof JButton) {
JButton button = (JButton) child;
if (text.equals(button.getText())) {
btn = button;
break;
}
} else if (child instanceof Container) {
children.add((Container) child);
}
}
if (btn == null) {
for (Container cont : children) {
JButton button = getButton(cont, text);
if (button != null) {
btn = button;
break;
}
}
}
return btn;
}
}
This example uses the doClick method of the JButton to simulate a clicking of the button. You could use the locationOnScreen information from the button and java.awt.Robot to physically click the button. but this just seemed simpler.
You could also take a look at Jemmy which is a utiliy library designed to make it easiert to test Swing (and JavaFX) based applications...
First, create a JDialog from a JOptionPane object. Then, create a timer to run for the time you want( exemple 1min) , and dispose the dialog once it has finished. Then, retrieve the chosen value from your JOptionPane object, making sure to account for an uninitialized value if the dialog was disposed by your timer.
final JOptionPane pane = new JOptionPane(......., JOptionPane.OK_CANCEL_OPTION);
final JDialog dialog = pane.createDialog(.....);
Timer timer = new Timer(you_time, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
dialog.dispose();
}
});
timer.start();
dialog.setVisible(true);
dialog.dispose();
Integer choice = (Integer) (pane.getValue() == JOptionPane.UNINITIALIZED_VALUE ? JOptionPane.OK_OPTION : pane.getValue());
*I'm now encountering a very strange java GC problem when I trying to make a button in a JFrame, and when I click the button, it display a JDialog which need to deal with and show some images and need nearly 200M memory. But the problem is when I close the dialog and reopen it, sometimes it cause java.lang.OutOfMemoryError. (not every times)
Trying to solve the problem, I simplify this problem and make some experiment, which cause me more confused.
The Code I used in my " experiment " is showed below.
When I click a button in a frame, I allocate 160M memory for an integer array, and display a dialog, But If I close the dialog and reopen it, OutOfMemoryError appears. I adjusting the code and the result is:
If I don’t create the dialog and show it, no memory problem.
If I add a windowsCloseListener which invoke System.gc() to the dialog, no memory problem.
If I invoke System.gc() in the run() method, memory problem shows.
public class TestController {
int[] tmp;
class TDialog extends JDialog {
public TDialog() {
super();
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// If I uncommment this code, OutOfMemoryError seems to dispear in this situation
// But I'm sure it not a acceptable solution
/*
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.out.println("windowsclose");
TDialog.this.dispose();
System.gc();
}
});
*/
}
}
TDialog dia;
public void run() {
// If I do System.gc() here, OutOfMemoryError still exist
// System.gc();
tmp = new int[40000000];
for (int i = 0; i < tmp.length; i += 10)
tmp[i] = new Random().nextInt();
dia = new TDialog();
dia.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(200, 200);
JButton button = new JButton("button");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestController controller = new TestController();
controller.run();
controller = null;
}
});
frame.add(button);
frame.setVisible(true);
}
});
}
}
I’ve read about a lot articles which describe how java’s GC work. I think if java trying to allocate some space in the heap and it do not have enough free space, java will do gc, and if a object can’t be accessed from the gc root through “GC graph”, in which a edge from u to v represent u have a reference to v, root is something in the a thread working stack, or native resources, It’s useless and qualified to be collected by java’s GC.
Now the problem is When I click the button and trying to create an Integer array, the Integer array I create last time is certainly qualified to be collected by java’s GC. So why it caused Error.
Apologize for Such A Long Description…I don’t have much tactics in asking problem, so just trying to make it clear.
Besides, The parameter I used to start jvm is “ java –Xmx256m”
You're allocating new int[40000000] before while tmp still holds the reference to the last int[40000000].
The order of operation in an expression like tmp = new int[40000] is:
new int[40000]
Assign the reference to the array to tmp
So in 1. tmp is still holding the reference to it's last value.
Try doing:
tmp = null;
tmp = new int[40000000];
Try this:
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class TestController {
private JFrame frame;
int[] tmp;
public TestController(JFrame frame) {
this.frame = frame;
}
public void finish() {
if (dia != null) {
dia.dispose();
}
tmp = null;
}
class TDialog extends JDialog {
public TDialog() {
super(frame, "Dialog", true);
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
}
}
TDialog dia;
public void run() {
tmp = new int[40000000];
for (int i = 0; i < tmp.length; i += 10)
tmp[i] = new Random().nextInt();
dia = new TDialog();
dia.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setSize(200, 200);
JButton button = new JButton("button");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestController controller = new TestController(frame);
controller.run();
// controller = null;
System.out.println("here");
controller.finish();
}
});
frame.add(button);
frame.setVisible(true);
}
});
}
}
where you clean out both the dialog and its data in the finish() method. The dialog again should be modal for this to work, else you'll need a WindowListener.
You state in comment:
But would you tell me what's wrong in my code? and what's the meaning of "be modal". I've read the api of Dialog's setModal method in java doc. it means " whether dialog blocks input to other windows when shown", seems not the same thing as you referred.
A modal dialog is in fact one that blocks input from the calling window, and in fact freezes code flow from the calling code as soon as the dialog is visible. Code then resumes once the dialog is no longer visible.
There's no magical solution to your problem with the dialog being modal per se, but it allows us to know exactly when the dialog is no longer visible -- the code resumes from where the dialog was set visible, and thus allows us to call clean-up code at this point. Here I call the finish() method.
If you don't want the dialog to be modal, then you'd need a WindowListener and listen for the dialog being closed, and then do your finish method call there.
All my code does is make sure that the int array is available for GC'ing before you create a new int array.
I have an Swing application in which the user can create and save documents. The application is deployed as a simple Jar-file.
The primary target platform is Windows Vista.
If the user has on opened and non-saved document in the application and the user is logging out from the Windows Vista machine I would like to halt the logging off process and ask the user if he wants to save to document before the Java application is terminated.
Is it possible to halt the logging off process on Windows Vista from an Java application?
I've tried the shutdown hook with no success.
Is it possible to halt the logging off process on Windows Vista from an Java application?
No, nor should it be possible. That's like the tail wagging the dog.
EDIT2
For those who followed the discussion I leave the first answers I had but it seems like they don't work. First find my real solution.
Ok, so I think this actually works but it is not totally acceptable as it used restricted part of the API (but it exists since Java 1.3 and is still present in Java 1.7). It uses sun.misc.Signal. Most of the code has been originally posted by Andrew Thompson.
import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import sun.misc.Signal;
import sun.misc.SignalHandler;
class TestShutDown {
static final String WINDOW_MODIFIED = "windowModified";
TestShutDown() {
final JFrame f = new JFrame("Log Off!");
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
System.err.println("Window closing");
handleQuit(f);
}
});
Signal.handle(new Signal("TERM"), new SignalHandler() {
#Override
public void handle(Signal arg0) {
handleQuit(f);
}
});
// bad practice, but not the point..
f.setSize(400, 200);
f.setLocationByPlatform(true);
f.setVisible(true);
}
protected static void handleQuit(final JFrame f) {
int result = JOptionPane.showConfirmDialog(f, "Close Me");
if (result == JOptionPane.OK_OPTION) {
System.exit(0);
}
}
public static void main(String[] args) {
// start the GUI on the EDT
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestShutDown();
}
});
}
}
SOLUTION EARLIER SUGGESTED (NOT WORKING ON LOGOFF)
I am assuming that you are using a GUI application with a JFrame.
In your JFrame, set the following:
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
Then, register a WindowAdapteron your JFrame. Override the windowClosing() method and from there open a blocking dialog to ask the user what he wants to do (Yes/NO/CANCEL). In case he chooses, YES, you save and then dispose the frame, in case he chooses NO, you just dispose the frame. If he chooses cancel, you don't do anything.
EDIT:
Here is some code and more details on what I was explaining and that have been brought by Andrew Thompson. All credits should go to him for the following:
Using this code:
import java.awt.event.*;
import javax.swing.*;
class TestShutDown {
TestShutDown() {
final JFrame f = new JFrame("Log Off!");
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.addWindowListener( new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
int result = JOptionPane.showConfirmDialog(f, "Close Me");
if (result==JOptionPane.OK_OPTION) {
System.exit(0);
}
}
});
// bad practice, but not the point..
f.setSize(400,200);
f.setLocationByPlatform(true);
f.setVisible(true);
}
public static void main(String[] args) {
// start the GUI on the EDT
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestShutDown();
}
});
}
}
Then telling Windows to shut down, I see..
Even more interestingly, after I hit Cancel ( heck, 'Searching for UFOs' was the next queued track in the player, and I was not about to reschedule it :), I could not click on the frame. It seemed as though it was blocked with an invisible modal dialog. I had to kill the VM to be rid of it.
It's been a long time since I had to deal with concurrent programming in Java (it was in concurrent programming college classes, actually) and I'm having what to seem some pretty basic problem. The sample code below might seem kinda weird since I'm not using standard JDK for UI controls, but it goes like this:
//class Screen
public class Screen{
private Frame rootContainer;
public Screen(Frame rootContainer){
this.rootContainer = rootContainer;
this.createGui();
}
private void createGui(){
Button btn = new Button("Call");
btn.setBounds(20, 20, 100, 20);
rootContainer.add(btn);
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ConcurrentDialog cr = createDialog();
cr.start();
//Suposedly only to be called after the Exit button in the dialog is clicked
((Button)e.getSource()).setLabel("Called");
((Button)e.getSource()).repaint();
}
});
}
private ConcurrentDialog createDialog(){
return new ConcurrentDialog(rootContainer, this);
}
}
//Class ConcurrentDialog
public class ConcurrentDialog extends Thread {
private Frame rootContainer;
private Screen screen;
public ConcurrentDialog(Frame rootContainer, Screen screen){
this.rootContainer = rootContainer;
this.screen = screen;
}
public void run(){
createDialog();
synchronized(screen){
try {
screen.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
private void createDialog(){
Dialog dialog = new Dialog(rootContainer, true);
dialog.setBounds(20, 20, 110, 35);
Button btn = new Button("Exit");
btn.setBounds(5, 5, 100, 20);
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Button source = (Button)e.getSource();
Dialog dialog = (Dialog)source.getParent();
synchronized(screen){
screen.notify();
}
dialog.dispose();
dialog.getOwner().remove(dialog);
dialog = null;
}
});
dialog.add(btn);
dialog.show();
}
}
Before anyone asks, yes, I'm trying to implement a modal dialog (in fact, I should rename ConcurrentDialog to ModalDialog). As I said before, I'm not using swing (just because I CAN'T... Embeded VM's are usually platform-specific when it comes to UI and that's my case) and this particular library doesn't have a native Modal Dialog (meaning no JOptionPane.showMessageDialog love for me) so I'm building one from scratch.
Anyway, here's the problem: It seems that the wait() method is executed much later than ((Button)e.getSource()).setLabel("Called"). A workaround that I found is setting btn as a global attribute with public access and refactor the run() method to this:
public void run(){
createDialog();
synchronized(screen){
try {
screen.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
screen.getBtn().setLabel("Called");
screen.getBtn().repaint();
}
It works this way, but in my "real world" scenario, this implementation would cause quite a mess when it comes to coupling. Any pointers?
your cr.start() returns immediately.
What you want is to put the wait(screen) instead of after cr.start(), and remove wait(screen) from run() method.
This way the thread will show a dialog and exit. Once the dialog is closed, screen will be notified, and your createGui().actionPerformed() will wake up.
you can't use multiple threads with swing/awt, and you can't "wait" on the thread which is running the gui, or it will stop working. the way to do asynchronous work which involves the gui is to spawn a separate thread (as you are doing), and then have that thread use SwingUtilities.invokeLater() whenever it needs to "update" the gui.