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() );
}
}
As shown here, there is an irreducible leak due to unrecoverable allocations related to a typical host peer component. The remnant is ~2 MB in the course of creating and disposing ~103 windows. In your case, the dominant leak is due to retained instances of
bigMemoryChunk
. One approach is to make the instances unreachable in aWindowListener
.JFrame
has no direct way to know that that each instance in your program has an associated instance ofbigMemoryChunk
. Such an object becomes eligible for garbage collection when it is unrechable;bigMemoryChunk
is the only reference to the array object in this case, so setting it tonull
makes it immediately eligible for later garbage collection.You may be confusing containment with inheritance and composition. The
JFrame
isn't "holding a reference tobigMemoryChunk
;" theJFrame
has an instance variable namedbigMemoryChunk
that holds a reference to an array object. The small amount of memory lost to the frame's peer is owned and managed by the host. The large amount of memory inbigMemoryChunk
is your program's responsibility. The enclosedWindowListener
allows you to associate management of the array object with closing the frame.The profile below shows a series of four subframes opened; each one is then closed, followed by a forced garbage collection in the profiler.
As profiled: