java - Swing Timer 不被垃圾回收

标签 java swing timer garbage-collection

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

我使用计时器在Page1中显示图像的幻灯片。

当我分析应用程序时,我注意到导航到 Page2 时,Timer 对象、Page1 对象以及 Page1 对象中的任何对象都没有被垃圾收集。

我开始知道stopping计时器允许它被垃圾收集。

我假设如果任何对象没有被引用,则它已准备好进行垃圾收集。但这个假设在本例中失败了。

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

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

可能的原因是什么?

最佳答案

我在一个人为的小堆中分析了您的示例,如图所示here 。配置文件显示了预期的结果:定期垃圾收集将已用堆空间返回到基线,如图 here 所示。 。选择第二页作为轮廓的后半部分会导致较小的幅度收集。内存采样显示,第一页上出现的 Timer 实例在第二页上被立即收集;没有实例激增。一些额外的建议:

image

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

结果相同。请注意 Swing Timer 的实例“共享相同的、预先存在的计时器线程。”当 Timer 运行时,内部类 DoPostEvent 的实例出现在探查器中的名称为 javax.swing.Timer$1 的,将暂时累积。它们也将被收集,尽管最终是在垃圾收集的后期阶段。按照建议here ,您可以单击执行GC按钮来实现更积极的收集。单击 Sampler 选项卡中的 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");
    }
}

关于java - Swing Timer 不被垃圾回收,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45121837/

相关文章:

发送带有附件的电子邮件时出现 java.lang.LinkageError

java - 在 intellij 中运行测试时将属性传递给 Maven 配置文件

java - Swing 中的垂直进度条?

c - Windows native API 是否支持计时器?

java - 向 KeyListener 添加计时器

java - 将 Java 字节转换为 Perl

java - 如何在 Java 中访问 JSON 数据的嵌套元素?

java - 更改 JMenuItem 上的 JPanel 单击(ActionListener 未触发)

java - 向表中添加值并将其添加到主窗体

javascript - JavaScript 间隔什么时候停止?