I have an application that needs to open multiple JFrames (it's a log viewer, and sometimes you need to see a bunch of logs in separate windows to compare).
It appears that the JVM (Java 8 update 101 on OS X) is holding a strong reference to the JFrame, which is preventing it from being garbage collected, and eventually leads to an OutOfMemoryError being thrown.
To see the problem, run this problem with a max heap size of 200 megabytes. Each time a window is opened, it consumes 50 megabytes of RAM. Open three windows (using 150 megabytes of RAM). Then close the three windows (which calls dispose), which should free up memory. Then try to open a fourth window. An OutOfMemoryError is thrown and the fourth window does not open.
I've seen other answers stating that memory will be automatically released when necessary to avoid running out, but that doesn't seem to be happening.
package com.prosc.swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
public class WindowLeakTest {
public static void main(String[] args) {
EventQueue.invokeLater( new Runnable() {
public void run() {
JFrame launcherWindow = new JFrame( "Launcher window" );
JButton launcherButton = new JButton( "Open new JFrame" );
launcherButton.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
JFrame subFrame = new JFrame( "Sub frame" ) {
private byte[] bigMemoryChunk = new byte[ 50 * 1024 * 1024 ]; //50 megabytes of memory
protected void finalize() throws Throwable {
System.out.println("Finalizing window (Never called until after OutOfMemory is thrown)");
super.finalize();
}
};
subFrame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
subFrame.add( new JLabel( "Nothing to see here" ) );
subFrame.pack();
subFrame.setVisible( true );
System.out.println( "Memory usage after new window: " + getMemoryInfo() );
}
} );
launcherWindow.add( launcherButton );
launcherWindow.pack();
launcherWindow.setVisible( true );
new Timer( 5000, new ActionListener() {
public void actionPerformed( ActionEvent e ) {
System.gc();
System.out.println( "Current memory usage after garbage collection: " + getMemoryInfo() );
}
} ).start();
}
} );
}
public static String getMemoryInfo() {
NumberFormat numberFormat = NumberFormat.getNumberInstance();
return "Max heap size is " + numberFormat.format( Runtime.getRuntime().maxMemory() ) + "; free memory is " + numberFormat.format( Runtime.getRuntime().freeMemory() ) + "; total memory is " + numberFormat.format( Runtime.getRuntime().totalMemory() );
}
}