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