1

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

I have used Timer to display slideshow of images in 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.

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

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.

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?

Barun
  • 4,245
  • 4
  • 23
  • 29
  • *"I came across problem of memory leak.."* The only way to be sure of a memory leak is an `OutOfMEmoryError`. Does the app. throw one? The garbage collector is known to be quite conservative about what it will collect, and often only acts if it sees a need to do so. – Andrew Thompson Jul 15 '17 at 19:12
  • I was unable to identify a leak, as shown [here](https://stackoverflow.com/a/45124503/230513). – trashgod Jul 16 '17 at 02:51
  • @AndrewThompson While profiling in Netbeans, you can see counting of live objects. `OutOfMemoryError` is not the only way to confirm memory leak, although eventually it will occur. Also, I have added to explanation of code to my question. – Barun Jul 16 '17 at 03:40
  • This question was previously closed os a duplicate of [*Why does this not get garbage collected?*](https://stackoverflow.com/q/769368/230513), which examines `java.util.Timer`; reopened, as this question refers to `javax.swing.Timer`. – trashgod Jul 16 '17 at 13:38

1 Answers1

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:

image

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

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.

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");
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045