import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.util.Timer; import java.util.TimerTask; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; import javafx.stage.StageStyle; import javax.swing.*; /** * Steps to demonstrate the issue: * 1) Launch the application, observe the Swing window. * 2) Click the "GC" button to get a baseline memory reading. * 3) Click the "Open JavaFX Window", observing the JavaFX window that opens. * 4) Observe that the used memory reading has increased by about 5 megabytes, as expected. * 5) Close the JavaFX window. * 6) Click the "GC" button again, and observe that the memory does not go back to a * reasonable number (close to the baseline). If you observe this object inside of a * memory profiler, you can see that the byte[] is still strong-reachable. * 7) If running with a memory profiler, open another JavaFX window, and observe that the * previous JavaFX window is now garbage collected. * * @author lperkins */ public class WindowMemoryLeak { private static final double megabyte = (1024d * 1024d); private static void createAndShowFxGui() { // allocating 5 megabytes to simulate a large data model int byteCount = 5 * (int) megabyte; byte[] dataModel = new byte[byteCount]; // attach the data model to the UI so it doesn't get garbage // collected until the UI does Label label = new Label("Dummy Window Data"); label.setUserData(dataModel); // builders are getting removed in Java 8... /sadface BorderPane root = new BorderPane(); root.setCenter(label); // open the JavaFX window Stage stage = new Stage(StageStyle.DECORATED); stage.setTitle("JavaFX Window (Sub App)"); stage.setScene(new Scene(root, 150, 100)); stage.centerOnScreen(); stage.show(); } private static void createAndShowSwingGui() { // construct the Swing UI JPanel contentPanel = new JPanel(new BorderLayout(0, 10)); contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); contentPanel.add(new JButton(new AbstractAction("Open JavaFX Window") { @Override public void actionPerformed(ActionEvent e) { Platform.runLater(new Runnable() { @Override public void run() { createAndShowFxGui(); } }); } }), BorderLayout.NORTH); contentPanel.add(new SwingMemoryReadout(), BorderLayout.CENTER); // initializing the JavaFX toolkit contentPanel.putClientProperty("initializeFxToolkit", new JFXPanel()); // open the Swing window JFrame frame = new JFrame("Swing Window (Main App)"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(contentPanel); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowSwingGui(); } }); } private static class SwingMemoryReadout extends JPanel implements Runnable { private final JLabel currentLabel; private final JLabel maxLabel; private final Timer refreshTimer; public SwingMemoryReadout() { currentLabel = new JLabel(); maxLabel = new JLabel(); refreshTimer = new Timer("Refresh Memory Stats Timer"); refreshTimer.schedule(new TimerTask() { @Override public void run() { triggerStatsRefresh(); } }, 1000L, 1000L); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); add(currentLabel); add(Box.createHorizontalStrut(10)); add(maxLabel); add(Box.createHorizontalStrut(10)); add(new JButton(new AbstractAction("GC") { @Override public void actionPerformed(ActionEvent e) { triggerGc(); } })); run(); } public void triggerGc() { System.gc(); triggerStatsRefresh(); } public void triggerStatsRefresh() { SwingUtilities.invokeLater(this); } @Override public void run() { Runtime runtime = Runtime.getRuntime(); long maxMemory = runtime.maxMemory(); long totalMemory = runtime.totalMemory(); long freeMemory = runtime.freeMemory(); long usedMemory = totalMemory - freeMemory; currentLabel.setText(String.format("Used: %.2f MB", Double.valueOf(usedMemory / megabyte))); maxLabel.setText(String.format("Max: %.2f MB", Double.valueOf(maxMemory / megabyte))); } } // end class SwingMemoryReadout }