7

*JFrame でボタンを作成しようとすると、非常に奇妙な Java GC の問題が発生します。ボタンをクリックすると、いくつかの画像を処理して表示する必要があり、200M 近くのメモリを必要とする JDialog が表示されます。しかし、問題は、ダイアログを閉じて再度開くと、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 から v は u が v への参照を持っていることを表します. root はスレッドの作業スタックまたはネイティブ リソースにあるものです. Java の GC によって収集されるのは役に立たず、修飾されています.

さて問題は、ボタンをクリックして整数配列を作成しようとすると、前回作成した整数配列は確かにJavaのGCによって収集される資格があります。では、なぜエラーが発生したのか。

こんなに長い説明で申し訳ありません…問題を尋ねる戦術があまりないので、わかりやすくするようにしています。

また、jvm の起動に使用したパラメータは「java –Xmx256m」です。</p>

4

2 に答える 2

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 でダイアログの setModal メソッドの API を読みました。「ダイアログが表示されたときに他のウィンドウへの入力をブロックするかどうか」という意味ですが、あなたが言及したものとは異なるようです。

モーダル ダイアログは、実際には、呼び出しウィンドウからの入力をブロックするダイアログであり、実際には、ダイアログが表示されるとすぐに、呼び出しコードからのコード フローをフリーズします。ダイアログが表示されなくなると、コードが再開されます。

ダイアログ自体がモーダルであるという問題に対する魔法のような解決策はありませんが、ダイアログがいつ表示されなくなったかを正確に知ることができます。ダイアログが表示された場所からコードが再開されるため、 clean- を呼び出すことができます。この時点でコードをアップします。ここでメソッドを呼び出しますfinish()

ダイアログをモーダルにしたくない場合は、WindowListener が必要で、ダイアログが閉じられるのをリッスンしてから、finish メソッドの呼び出しをそこで行います。

私のコードは、新しい int 配列を作成する前に、int 配列が GC で使用できることを確認するだけです。

于 2013-10-25T19:43:13.550 に答える