7

*我现在在JFrame中创建一个按钮时遇到了一个非常奇怪的java GC问题,当我单击该按钮时,它会显示一个JDialog,需要处理和显示一些图像,需要近200M内存。但问题是当我关闭对话框并重新打开它时,有时会导致 java.lang.OutOfMemoryError。(不是每次)

为了解决这个问题,我简化了这个问题并做了一些实验,这让我更加困惑。

我在“实验”中使用的代码如下所示。当我点击一个框架中的一个按钮时,我为一个整数数组分配了 160M 内存,并显示了一个对话框,但是如果我关闭对话框并重新打开它,就会出现 OutOfMemoryError。我调整了代码,结果是:

  1. 如果我不创建对话框并显示它,则没有内存问题。
  2. 如果我在对话框中添加一个调用 System.gc() 的 windowsCloseListener,则没有内存问题。
  3. 如果我在 run() 方法中调用 System.gc(),则会显示内存问题。

    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);
          }
        });
      }
    }
    

我已经阅读了很多描述 java 的 GC 工作原理的文章。我认为如果java试图在堆中分配一些空间并且它没有足够的可用空间,java会做gc,如果一个对象不能通过“GC图”从gc根访问,其中一条边来自u to v 代表你对v有引用,root是线程工作栈中的东西,或者原生资源,没用的,有资格被java的GC收集。

现在的问题是当我点击按钮并尝试创建一个Integer数组时,我上次创建的Integer数组肯定有资格被java的GC收集。那么为什么它会导致错误。

为这么长的描述道歉……我在提出问题时没有太多策略,所以只是想把它说清楚。

另外,我用来启动jvm的参数是“java –Xmx256m”</p>

4

2 回答 2

3

你在new int[40000000]之前分配,而tmp仍然持有对 last 的引用 int[40000000]
表达式中的操作顺序如下tmp = new int[40000]

  1. new int[40000]
  2. 将数组的引用分配给tmp

所以在1. tmp中仍然持有对它的最后一个值的引用。

尝试做:

tmp = null;
tmp = new int[40000000];
于 2013-10-25T19:40:51.540 回答
2

尝试这个:

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);
         }
      });
   }
}

您在方法中清除对话框及其数据的位置finish()。该对话框再次应该是模态的,否则您将需要一个 WindowListener。


你在评论中说:

但是你能告诉我我的代码有什么问题吗?以及“模态”的含义是什么。我在 java doc 中阅读了 Dialog 的 setModal 方法的 api。它的意思是“对话框是否在显示时阻止输入到其他窗口”,似乎与您提到的不同。

模态对话框实际上是一个阻止来自调用窗口的输入的对话框,并且实际上只要对话框可见就冻结来自调用代码的代码流。一旦对话框不再可见,代码就会恢复。

对于对话框本身是模态的问题,没有神奇的解决方案,但它可以让我们准确地知道对话框何时不再可见 - 代码从对话框设置为可见的位置恢复,因此允许我们调用 clean-在这一点上代码。这里我称之为finish()方法。

如果您不希望对话框是模态的,那么您需要一个 WindowListener 并侦听正在关闭的对话框,然后在那里调用您的完成方法。

我的所有代码都是在创建新的 int 数组之前确保 int 数组可用于 GC'ing。

于 2013-10-25T19:43:13.550 回答