3

When I have multiple SwingNodes in a panel (GridPane in a JFXPanel), I notice an extreme performance degradation. This doesn't appear to occur if there's only ONE SwingNode.

I realize that there's some expected degradation when mixing Swing and JavaFX, but this renders the application pretty much unusable (and I can't really change my industry circumstances; the legacy code is still in Swing, but we really want the new JavaFX graphing utilities).

This is running on Windows 7 on Java 8u60.

MCVE:

import javax.swing.*;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;

public class SwingNodeTest {
    private static Scene createScene(JComponent button1, JComponent button2) {
        GridPane pane = new GridPane();
        pane.getColumnConstraints().add(new ColumnConstraints(100));
        pane.getColumnConstraints().add(new ColumnConstraints(200));

        SwingNode node1 = new SwingNode();
        // Best practice to call SwingNode->setContent(...) on the EDT, but doesn't make
        // a difference for the test.
        node1.setContent(button1);
        pane.add(node1, 0, 0);

        ToggleButton node2 = new ToggleButton("2");
        // Commenting out the above line and uncommenting the below lines cause EXTREME
        // Performance degradation.
//        SwingNode node2 = new SwingNode();
//        node2.setContent(button2);
        pane.add(node2, 1, 0);

        return new Scene(pane);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                JFXPanel panel = new JFXPanel();
                frame.setSize(800, 600);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setContentPane(panel);
                frame.setVisible(true);

                JButton button1 = new JButton("1");
                JToggleButton button2 = new JToggleButton("2");

                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        final Scene scene = createScene(button1, button2);
                        panel.setScene(scene);
                    }
                });
            }
        });
    }
}

EDIT: Just realized after letting this MCVE run for a few minutes, I get OutOfMemoryErrors (GC overhead limit exceeded).

Ironcache
  • 1,719
  • 21
  • 33
  • 1
    It looks like a `JFrame` with an embedded `JFXPanel` with embedded `SwingNode` objects causes this problem. Using Eclipse MAT and `-XX:+HeapDumpOnOutOfMemoryError` I see that the JavaFX event dispatch queue becomes huge. However if I rework to code to use JavaFX `Application`, and get rid of the `JFrame` and `JFXPanel`, then all is good. Is this a possibility for you? – clstrfsck Nov 27 '15 at 04:48
  • The piece of software I'm working on is heavily-invested into Swing. A complete overhaul of the software into JavaFX is not feasible, at least not in the short term. – Ironcache Nov 27 '15 at 04:54
  • Wrapping the application in JavaFX might be more feasible (IE: launching the Swing application as a JavaFX application in a SwingNode). Seems like that's not likely to fix the issue though; instead of Swing containing JavaFX containing Swing, I'd have JavaFX containing Swing containing JavaFX containing Swing, which I can't imagine is better. I'll test it when I get in the office all the same. – Ironcache Nov 27 '15 at 04:59
  • After thinking about the software situation we have at my office a bit more, I've realized that wrapping the application is also not feasible in the short term (the frame is actually loaded by software external to my team/project). I'm likely stuck with a JFrame as the top-level container for the time being. – Ironcache Nov 27 '15 at 05:15
  • See also [*Initial Threads*](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html), for [example](http://stackoverflow.com/a/31576647/230513). – trashgod Nov 27 '15 at 05:24
  • Sorry if I've misinterpreted the point, but surrounding the contents of main with SwingUtils.invokeLater() does not alleviate the issue. It was an omission from the MCVE which I'll edit in. – Ironcache Nov 27 '15 at 05:33
  • Reproduced on Mac OS X 10.11.1, Java 1.8 even with the missing `EventQueue.invokeLater()` in `createScene()`. – trashgod Nov 27 '15 at 12:30
  • 1
    When profiling, the problem does not occur until a Swing component gains focus. – trashgod Nov 27 '15 at 15:06

1 Answers1

1

I submitted this as a bug. As of the time of writing, it's currently open and in the JDK backlog:

https://bugs.openjdk.java.net/browse/JDK-8144504

Unfortunately, while this bug is open, the only work-around seems to be to limit your Swing to JavaFX to Swing integration. We ended up remaking all of our Swing widgets that were being used in the JavaFX panel in JavaFX, which was a fairly substantial overhaul.

For the reference of anyone who wandered in here looking for a solution to this problem, I'm also going to post this JDK bug, which is also open as of the time of writing:

https://bugs.openjdk.java.net/browse/JDK-8136530

The bug states that, even if you only have one SwingNode in a JavaFX Pane in a JFXPanel, your CPU usage will dramatically increase (but the freezing of multiple nodes does not occur). This ended up biting us after we thought we cleverly got around the issue by limiting the number of SwingNode instances in our JavaFX Pane. Hopefully, this heads up will help prevent others from face-planting into it like we did.

EDIT (21/09/2017): The freezing bug is now listed as fixed with a fix version of Java 10. The CPU usage increase bug is still in an open state.

Ironcache
  • 1,719
  • 21
  • 33