java garbage collecting in dialog

2019-03-20 03:14发布

问题:

*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:

  1. If I don’t create the dialog and show it, no memory problem.
  2. If I add a windowsCloseListener which invoke System.gc() to the dialog, no memory problem.
  3. 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”

回答1:

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:

  1. new int[40000]
  2. 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];


回答2:

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.