由于 Swing Timer,我在 Swing 应用程序中遇到了内存泄漏问题.
我使用计时器在Page1中显示图像的幻灯片。
当我分析应用程序时,我注意到导航到 Page2 时,Timer 对象、Page1 对象以及 Page1 对象中的任何对象都没有被垃圾收集。
我开始知道stopping计时器允许它被垃圾收集。
我假设如果任何对象没有被引用,则它已准备好进行垃圾收集。但这个假设在本例中失败了。
下面的代码总结了我的应用程序,并且没有内存泄漏。要查看内存泄漏,请注释我调用 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");
}
可能的原因是什么?
最佳答案
我在一个人为的小堆中分析了您的示例,如图所示here 。配置文件显示了预期的结果:定期垃圾收集将已用堆空间返回到基线,如图 here 所示。 。选择第二页作为轮廓的后半部分会导致较小的幅度收集。内存采样显示,第一页上出现的 Timer 实例在第二页上被立即收集;没有实例激增。一些额外的建议:
仅在 event dispatch thread 上构造和操作 Swing GUI 对象.
考虑使用
CardLayout
动态切换页面。
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/