Swing Timer不是垃圾回收

时间:2021-08-25 23:59:23

I came across problem of memory leak in Swing Application due to Swing Timer.

由于Swing Timer,我在Swing应用程序中遇到了内存泄漏问题。

I have used Timer to display slideshow of images in Page1.

我用Timer来显示Page1中图像的幻灯片。

When I profiled the application, I noticed that when navigating to Page2, the Timer object, the Page1 object and any object within Page1 object were not Garbage Collected.

当我分析应用程序时,我注意到当导航到Page2时,Timer对象,Page1对象和Page1对象中的任何对象都不是Garbage Collected。

I came to know that stopping the Timer allows it to be garbage collected.

我开始知道停止Timer会让它被垃圾收集。

I was assuming that if any object is not being referenced, it is ready for garbage collection. But this assumption failed in this case.

我假设如果没有引用任何对象,它就可以进行垃圾回收了。但在这种情况下,这种假设失败了。

The code below summarizes my application and does not have memory leak. To see memory leak, comment the line where I have called stopTimer method of Timer.

下面的代码总结了我的应用程序,没有内存泄漏。要查看内存泄漏,请注释我调用Timer的stopTimer方法的行。

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

What could be the possible reason for this?

可能的原因是什么?

1 个解决方案

#1


2  

I profiled your example in an artificially small heap, as shown here. The profile showed the expected result: periodic garbage collection returns the used heap space to baseline, as shown here. Selecting page two for the last half of the profile resulted in smaller amplitude collections. Sampling memory showed that the Timer instance present on page one was collected promptly on page two; no instances proliferated. Some additional suggestions:

我在一个人工小的堆中描述了你的例子,如下所示。该配置文件显示了预期的结果:定期垃圾收集将使用的堆空间返回到基线,如此处所示。为轮廓的后半部分选择第二页导致较小的振幅收集。采样内存显示第一页上的Timer实例已在第二页上迅速收集;没有实例激增。一些额外的建议:

  • Construct and manipulate Swing GUI objects only on the event dispatch thread.

    仅在事件派发线程上构造和操作Swing GUI对象。

  • Consider using CardLayout to switch pages dynamically.

    考虑使用CardLayout动态切换页面。

Swing Timer不是垃圾回收

Comment [out] the line where I have called [the] stopTimer() method.

注释[out]我称之为[stop]定时器()方法的行。

The same result prevails. Note that instances of Swing Timer "share the same, pre-existing timer thread." As the Timer runs, instances of the inner class DoPostEvent, appearing in the profiler with the name javax.swing.Timer$1, will accumulate transiently. They too will be collected, albeit eventually in a later phase of garbage collection. As suggested here, you can click the Perform GC button to effect a more aggressive collection. Click the Deltas button in the Sampler tab to see other instances that accumulate transiently in the course of executing the timer's ActionListener; click Perform GC again to see the effect.

同样的结果占上风。请注意,Swing Timer的实例“共享相同的,预先存在的计时器线程”。当Timer运行时,内部类DoPostEvent的实例将出现在探查器中,名称为javax.swing.Timer $ 1,它们将暂时累积。它们也将被收集,尽管最终将在垃圾收集的后期阶段收集。如此处所示,您可以单击“执行GC”按钮以实现更积极的收集。单击Sampler选项卡中的Deltas按钮,查看在执行计时器的ActionListener过程中暂时累积的其他实例;再次单击“执行GC”以查看效果。

Console:

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

Code, as tested:

代码经测试:

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

#1


2  

I profiled your example in an artificially small heap, as shown here. The profile showed the expected result: periodic garbage collection returns the used heap space to baseline, as shown here. Selecting page two for the last half of the profile resulted in smaller amplitude collections. Sampling memory showed that the Timer instance present on page one was collected promptly on page two; no instances proliferated. Some additional suggestions:

我在一个人工小的堆中描述了你的例子,如下所示。该配置文件显示了预期的结果:定期垃圾收集将使用的堆空间返回到基线,如此处所示。为轮廓的后半部分选择第二页导致较小的振幅收集。采样内存显示第一页上的Timer实例已在第二页上迅速收集;没有实例激增。一些额外的建议:

  • Construct and manipulate Swing GUI objects only on the event dispatch thread.

    仅在事件派发线程上构造和操作Swing GUI对象。

  • Consider using CardLayout to switch pages dynamically.

    考虑使用CardLayout动态切换页面。

Swing Timer不是垃圾回收

Comment [out] the line where I have called [the] stopTimer() method.

注释[out]我称之为[stop]定时器()方法的行。

The same result prevails. Note that instances of Swing Timer "share the same, pre-existing timer thread." As the Timer runs, instances of the inner class DoPostEvent, appearing in the profiler with the name javax.swing.Timer$1, will accumulate transiently. They too will be collected, albeit eventually in a later phase of garbage collection. As suggested here, you can click the Perform GC button to effect a more aggressive collection. Click the Deltas button in the Sampler tab to see other instances that accumulate transiently in the course of executing the timer's ActionListener; click Perform GC again to see the effect.

同样的结果占上风。请注意,Swing Timer的实例“共享相同的,预先存在的计时器线程”。当Timer运行时,内部类DoPostEvent的实例将出现在探查器中,名称为javax.swing.Timer $ 1,它们将暂时累积。它们也将被收集,尽管最终将在垃圾收集的后期阶段收集。如此处所示,您可以单击“执行GC”按钮以实现更积极的收集。单击Sampler选项卡中的Deltas按钮,查看在执行计时器的ActionListener过程中暂时累积的其他实例;再次单击“执行GC”以查看效果。

Console:

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

Code, as tested:

代码经测试:

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