2017-07-15 21 views
1

Swing TimerのためにSwingアプリケーションでメモリリークの問題が発生しました。スイングタイマーはガベージコレクションされていません

私は、タイマーを使用して画像のスライドショーをPage1に表示しました。

Page2に移動すると、Timerオブジェクト、Page1オブジェクト、およびPage1オブジェクト内のオブジェクトはガベージコレクトされていないことに気付きました。

私は、stoppingがタイマーによってガベージコレクションされることができることを知りました。

オブジェクトが参照されていない場合は、ガベージコレクションの準備が整っていると仮定していました。しかし、この仮定はこのケースでは失敗しました。

以下のコードは私のアプリケーションをまとめたもので、メモリリークはありません。メモリリークを確認するには、stopTimerというメソッドをTimerという名前の行にコメントします。

import java.awt.BorderLayout; 
import java.awt.Container; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.Timer; 

public class TimerMemoryLeak { 

    public static void main(String[] args) { 
     TimerMemoryLeak timer = new TimerMemoryLeak(); 
     timer.buildUI(); 
    } 

    public void buildUI() { 
     showPanel1(); 

     frame.setSize(600, 400); 
     frame.setVisible(true); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    } 

    public void showPanel1() { 
     Page1 page1 = new Page1(); 
     if (currentPanel != null) { 
      pane.remove(((Page2) currentPanel).getPanel()); 
     } 
     pane.add(page1.getPanel()); 
     currentPanel = page1; 
     page1.startTimer(); 

     page1.setNextAction(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       showPanel2(); 
      } 
     }); 

     pane.revalidate(); 
     pane.repaint(); 
    } 

    public void showPanel2() { 
     Page2 page2 = new Page2(); 
     if (currentPanel != null) { 
      ((Page1) currentPanel).stopTimer(); // Comment this for memory leak 
      pane.remove(((Page1) currentPanel).getPanel()); 
     } 
     pane.add(page2.getPanel()); 
     currentPanel = page2; 

     page2.setPreviousAction(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       showPanel1(); 
      } 
     }); 

     pane.revalidate(); 
     pane.repaint(); 
    } 

    private JFrame frame = new JFrame(); 
    private Container pane = frame.getContentPane(); 

    private Object currentPanel; 
} 

class Page1 { 

    public Page1() { 
     panel.add(title, BorderLayout.NORTH); 
     panel.add(textTimer); 
     panel.add(btnNext, BorderLayout.SOUTH); 
    } 

    public void setNextAction(ActionListener listener) { 
     btnNext.addActionListener(listener); 
    } 

    public JPanel getPanel() { 
     return panel; 
    } 

    public void startTimer() { 
     timer.setInitialDelay(0); 
     timer.start(); 
    } 

    public void stopTimer() { 
     timer.stop(); 
    } 

    private JPanel panel = new JPanel(new BorderLayout()); 
    private JLabel title = new JLabel("Panel 1"); 
    private JButton btnNext = new JButton("Next"); 
    private JLabel textTimer = new JLabel(); 
    private int timerInterval = 1000; 
    private ActionListener timerAction = new ActionListener() { 
     @Override 
     public void actionPerformed(ActionEvent e) { 
      textTimer.setText(Math.random() + ""); 
     } 
    }; 
    private Timer timer = new Timer(timerInterval, timerAction); 
} 

class Page2 { 

    public Page2() { 
     panel.add(title, BorderLayout.NORTH); 
     panel.add(btnPrev, BorderLayout.SOUTH); 
    } 

    public void setPreviousAction(ActionListener listener) { 
     btnPrev.addActionListener(listener); 
    } 

    public JPanel getPanel() { 
     return panel; 
    } 

    private JPanel panel = new JPanel(new BorderLayout()); 
    private JLabel title = new JLabel("Panel 2"); 
    private JButton btnPrev = new JButton("Previous"); 
} 

これにはどのような原因が考えられますか?

+0

* "私はメモリリークの問題に出くわした.." *メモリリークの確認するための唯一の方法は、 'ですOutOfMEmoryError'。アプリはありますか? 1つを投げる?ガベージコレクタは、収集するものについては非常に保守的であることが知られており、頻繁に行う必要がある場合にのみ動作します。 –

+0

[here](https://stackoverflow.com/a/45124503/230513)に示すように、リークを特定できませんでした。 – trashgod

+0

@AndrewThompson Netbeansでプロファイリングする間に、ライブオブジェクトの数を見ることができます。 'OutOfMemoryError'はメモリリークを確認する唯一の方法ではありませんが、最終的に発生します。また、私は質問のコードの説明に追加しました。 – Barun

答えて

2

hereのように人工的に小さなヒープでサンプルをプロファイリングしました。プロファイルは、期待される結果を示しました。定期的なガベージコレクションは、hereのように、使用されたヒープスペースをベースラインに戻します。プロファイルの最後の半分についてページ2を選択すると、振幅コレクションが小さくなりました。サンプリングメモリは、ページ1にあるTimerインスタンスが2ページ目にすぐに収集されたことを示しました。インスタンスは増殖しなかった。いくつかの追加提案:

  • 構築し、SwingのGUIがのみevent dispatch thread上のオブジェクト操作します。

  • CardLayoutを使用してページを動的に切り替えることを検討してください。

image

コメント[出力]私は[インクルード] stopTimer()法と呼ばれているライン。

同じ結果が優先されます。スイングTimerのインスタンスは、同じ、既存のタイマースレッドを共有することに注意してください。 Timerが実行されると、javax.swing.Timer$1という名前のプロファイラに表示される内部クラスDoPostEventのインスタンスは一時的に累積されます。彼らも最終的にガベージコレクションの後のフェーズにもかかわらず、収集されます。 hereのように、Perform GCボタンをクリックすると、より積極的な収集を行うことができます。 サンプラータブのDeltasボタンをクリックして、タイマーActionListenerの実行中に過渡的に累積するその他のインスタンスを表示します。 を再度クリックして、GCを再度実行して、その影響を確認します。

コンソール:テストとして

$ jvisualvm & 
$ java TimerMemoryLeak.java 
$ java -Xms32m -Xmx32m TimerMemoryLeak 

コード:

import java.awt.BorderLayout; 
import java.awt.Container; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.Timer; 

public class TimerMemoryLeak { 

    public static void main(String[] args) { 
     TimerMemoryLeak timer = new TimerMemoryLeak(); 
     timer.buildUI(); 
    } 

    public void buildUI() { 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     showPanel1(); 
     frame.setSize(600, 400); 
     frame.setVisible(true); 
    } 

    public void showPanel1() { 
     Page1 page1 = new Page1(); 
     if (currentPanel != null) { 
      pane.remove(((Page2) currentPanel).getPanel()); 
     } 
     pane.add(page1.getPanel()); 
     currentPanel = page1; 
     page1.startTimer(); 

     page1.setNextAction(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       showPanel2(); 
      } 
     }); 
     pane.revalidate(); 
     pane.repaint(); 
    } 

    public void showPanel2() { 
     Page2 page2 = new Page2(); 
     if (currentPanel != null) { 
      ((Page1) currentPanel).stopTimer(); 
      pane.remove(((Page1) currentPanel).getPanel()); 
     } 
     pane.add(page2.getPanel()); 
     currentPanel = page2; 

     page2.setPreviousAction(new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       showPanel1(); 
      } 
     }); 

     pane.revalidate(); 
     pane.repaint(); 
    } 

    private JFrame frame = new JFrame(); 
    private Container pane = frame.getContentPane(); 
    private Object currentPanel; 

    private static class Page1 { 

     public Page1() { 
      panel.add(title, BorderLayout.NORTH); 
      panel.add(textTimer); 
      panel.add(btnNext, BorderLayout.SOUTH); 
     } 

     public void setNextAction(ActionListener listener) { 
      btnNext.addActionListener(listener); 
     } 

     public JPanel getPanel() { 
      return panel; 
     } 

     public void startTimer() { 
      timer.setInitialDelay(0); 
      timer.start(); 
     } 

     public void stopTimer() { 
      timer.stop(); 
     } 

     private JPanel panel = new JPanel(new BorderLayout()); 
     private JLabel title = new JLabel("Panel 1"); 
     private JButton btnNext = new JButton("Next"); 
     private JLabel textTimer = new JLabel(); 
     private int timerInterval = 1000; 
     private ActionListener timerAction = new ActionListener() { 
      @Override 
      public void actionPerformed(ActionEvent e) { 
       textTimer.setText(Math.random() + ""); 
      } 
     }; 
     private Timer timer = new Timer(timerInterval, timerAction); 
    } 

    private static class Page2 { 

     public Page2() { 
      panel.add(title, BorderLayout.NORTH); 
      panel.add(btnPrev, BorderLayout.SOUTH); 
     } 

     public void setPreviousAction(ActionListener listener) { 
      btnPrev.addActionListener(listener); 
     } 

     public JPanel getPanel() { 
      return panel; 
     } 

     private JPanel panel = new JPanel(new BorderLayout()); 
     private JLabel title = new JLabel("Panel 2"); 
     private JButton btnPrev = new JButton("Previous"); 
    } 
} 
+0

私はコードについていくつかの説明を追加しました。私が 'stopTimer'メソッドと呼んだ行をコメントしてみてください。 – Barun

+0

私はあなたの懸念の原因を見ています。私は上で詳述しました。 – trashgod

関連する問題