2

I've spent the past 2 days trying to debug a memory leak in my application and have now narrowed it down to one JFrame that is not being properly disposed.

It is started from a Control Frame via the following snippet:

startButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            new LeakingInterface();
        }
    });

The Frame itself has setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); set.

It contains a Button that saves the changes made within the Frame and then disposes as well as the typical close button. The "save changes"-button is worded as follows:

saveChanges.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
            saveChanges();
            dispose();
        }
    });

Now to my problem: The Frame is not properly unloaded from memory. I debugged this with the Eclipse Memory Analyzer. When closing the Frame via the OS-provided close button none of the Frames are unloaded/deleted, the "probable leaks"-screen says the following after and closing the frame 11 times:

11 instances of "LeakingInterface", loaded by "sun.misc.Launcher$AppClassLoader @ 0x6c00f08b8" occupy 393.71 MB (97,10%) bytes. 
These instances are referenced from one instance of "java.lang.Thread", loaded by "<system class loader>"

Weirdly enough closing screens via the save changes button unloads some of the JFrames and the "probable leaks"-screen gives me different answers. Again for 11 Frames loaded and closed:

One instance of "LeakingInterface" loaded by "sun.misc.Launcher$AppClassLoader @ 0x6c01bc1f8" occupies 36.21 MB (19,20%) bytes. The memory is accumulated in one instance of "LeakingInterface" loaded by "sun.misc.Launcher$AppClassLoader @ 0x6c01bc1f8".

This message shows up 5 times, but only 5 times and the occupied memory is much smaller than closing by OS exit button.

I'm at the end of my wits now what causes this and how I can fix it. The debugging was done on Mac OS but the same behaviour (Memory Leak) can be seen on Windows as well.

Things I have tried that produced no result:

1) Converting "LeakingInterface" into a singleton class like this:

public static LeakingInterface showCardChangeInterface(){
    if(instance != null){
        instance.dispose();
        instance = null;
        System.gc(); //Trying to make sure the gc runs at least once
    }
    instance = new LeakingInterface();
    return instance;
}

2) Instead of using just a constructor to show the Frame instead convert the frame into a field and making sure it is properly nulled and disposed before re-opening:

startButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            if(leakingFrame != null){
                leakingFrame.dispose();
            }
            leakingFrame = new LeakingFrame();
        }
    });

3) Trying to replicate at least some of the frames properly disposing I tried to overload the OS Button to mirror the behaviour of the saveChanges button.

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent event) {
       saveChanges();
       dispose();
    }
});

I'm very confused how this can provide different results.

E: As suggested here is a minimal example that can reproduce this behavior:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JFrame;

public class LeakingFrame extends JFrame{
public static LeakingFrame instance;
private ArrayList<String> content;

public static void main(String[] args) {
    JFrame testFrame = new JFrame("test");
    testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JButton startButton = new JButton("Open LeakingFrame");
    startButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            LeakingFrame.showInstance();
        }
    });
    testFrame.add(startButton);
    testFrame.pack();
    testFrame.setVisible(true);
}

public static LeakingFrame showInstance(){
    if(instance!=null){
        instance.dispose();
        instance = null;
        System.gc();
    }
    instance = new LeakingFrame();
    return instance;
}

private LeakingFrame(){
    super("LeakingFrame");
    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent event) {
           dispose();
        }
    });
    content = new ArrayList<String>();
    for(int i=0;i<150000;i++){
        content.add("LARGESTRING"); //So it takes up at least some amount of memory
    }
    JButton dispose = new JButton("Dispose");
    dispose.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            dispose();
        }
    });
    add(dispose);
    pack();
    setVisible(true);
}
}
  • All closed/disposed frames are referenced by the Java using the `WeakReference`. This means, that the window is completly unload from memory only when too few memory left or when garbage collector is called. So I think your memory leak has another cause. – Sergiy Medvynskyy May 24 '18 at 11:53
  • For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson May 24 '18 at 11:54
  • Why are these Frames not being unloaded when I specifically call System.gc() then? And why is the behaviour different when closing the Frame via OS exit button versus closing it via the Save changes button. – Kevin Wagner May 24 '18 at 11:55
  • This is the first place where I would look for a memory leak. You need to compare your code which is executed on OS-close and code that is executed on button-close. Your memory leak is probably somewhere nearby. – Sergiy Medvynskyy May 24 '18 at 12:00
  • The code is exactly the same though. Both call saveChanges(); dispose(); Only one does it from inside an anonymous ActionListener, the other from an anonymous WindowAdapter. I added the code called on OS-close to the main post. – Kevin Wagner May 24 '18 at 12:01
  • 2
    In this case I can only repeat the post of @AndrewThompson: please provide a [mcve] so we can also reproduce your problem. – Sergiy Medvynskyy May 24 '18 at 12:12
  • Okay, I have done so now, the code snipped I posted in the above post replicates the same behavior, only on a much smaller scale – Kevin Wagner May 24 '18 at 12:27
  • please have to look at [Remove Top-Level Container on Runtime](https://stackoverflow.com/questions/6309407/remove-top-level-container-on-runtime), all are accesible until current JVM is loaded in memory – mKorbel May 24 '18 at 13:24

0 Answers0