2017-07-15 1 views
1

Ich stieß auf das Problem der Speicherverlust in Swing-Anwendung aufgrund Swing Timer.Swing Timer ist kein Müll gesammelt

Ich habe Timer verwendet, um Diashow von Bildern in Page1 anzuzeigen.

Als ich die Anwendung profilierte, bemerkte ich, dass beim Navigieren zu dem Timer-Objekt das Page1-Objekt und jedes Objekt in Page1-Objekt nicht Garbage Collected war.

Ich kam zu wissen, stopping der Timer ermöglicht es, Müll gesammelt werden.

Ich ging davon aus, dass wenn ein Objekt nicht referenziert wird, es für die Garbage Collection bereit ist. Aber diese Annahme ist in diesem Fall gescheitert.

Der folgende Code fasst meine Anwendung zusammen und hat keinen Speicherverlust. Um Speicherleck zu sehen, kommentieren Sie die Zeile, in der ich stopTimer Methode von Timer aufgerufen habe.

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

Was könnte der mögliche Grund dafür sein?

+0

* "Ich habe dieses Problem der Speicherverlust .." * Die einzige Möglichkeit, sicher sein, einen Speicherverlust zu sein, ist ein ' OutOfMEmoryError'. Funktioniert die App? einen werfen? Es ist bekannt, dass der Müllsammler ziemlich konservativ ist bezüglich dessen, was er sammelt, und oft handelt er nur dann, wenn er es für nötig hält. –

+0

Ich konnte kein Leck erkennen, wie hier [hier] (https://stackoverflow.com/a/45124503/230513). – trashgod

+0

@AndrewThompson Während der Profilerstellung in Netbeans können Sie Live-Objekte zählen. "OutOfMemoryError" ist nicht die einzige Möglichkeit, den Speicherleck zu bestätigen, obwohl dies letztendlich auftreten wird. Außerdem habe ich zur Erklärung des Codes meine Frage hinzugefügt. – Barun

Antwort

2

Ich profilierte Ihr Beispiel in einem künstlich kleinen Haufen, wie gezeigt here. Das Profil zeigte das erwartete Ergebnis: Bei der periodischen Garbage Collection wird der verwendete Heap-Space zur Baseline zurückgegeben, wie in der Abbildung here dargestellt. Die Auswahl von Seite zwei für die letzte Hälfte des Profils führte zu Sammlungen mit kleinerer Amplitude. Sampling-Speicher zeigte, dass die Timer Instanz auf Seite eins prompt auf Seite zwei gesammelt wurde; keine Instanzen vermehrten sich. Einige weitere Vorschläge:

image

Kommentar [out] die Zeile, wo ich genannt habe [die] stopTimer() Methode.

Das gleiche Ergebnis herrscht. Beachten Sie, dass Instanzen von Swing Timer "den gleichen, bereits vorhandenen Timer-Thread teilen". Wenn Timer ausgeführt wird, werden Instanzen der inneren Klasse DoPostEvent, die im Profiler mit dem Namen javax.swing.Timer$1 angezeigt wird, vorübergehend akkumuliert. Auch sie werden gesammelt, wenn auch in einer späteren Phase der Speicherbereinigung. Wie in here vorgeschlagen, können Sie auf die Schaltfläche Ausführen GC klicken, um eine aggressivere Sammlung zu erzielen. Klicken Sie auf die Deltas Schaltfläche im Sampler Registerkarte, um andere Instanzen zu sehen, die vorübergehend im Laufe der Ausführung des Timers ActionListener akkumulieren; Klicken Sie auf . Führen Sie GC erneut aus, um den Effekt zu sehen.

Console:

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

-Code, wie geprüft:

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

Ich habe einige Erläuterungen zum Code hinzugefügt. Versuchen Sie, die Zeile zu kommentieren, in der ich die 'stopTimer'-Methode aufgerufen habe. – Barun

+0

Ich sehe die Quelle Ihrer Bedenken; Ich habe oben ausgearbeitet. – trashgod

Verwandte Themen