*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 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”
Java garbage collection is an automatic process. The programmer does not need to explicitly mark objects to be deleted. The garbage collection implementation lives in the JVM.
Unfortunately, this desire to immediately free up memory must go unrequited, as there is no way to force garbage collection (GC) in Java.
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]
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With