Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8259046

ViewPainter.ROOT_PATHS holds reference to Scene causing memory leak

XMLWordPrintable

    • generic
    • generic
    • Not verified

        A DESCRIPTION OF THE PROBLEM :
        It appears that it is possible for object references to remain through ViewPainter.ROOT_PATHS after adding/removing a JFXPanel. Oddly I’ve only ever been able to see the problem when a Text node is involved.

        For some context, I’m working on a layered desktop application written entirely in Swing. The application has layers added and removed on demand by the user. As a consequence of this, layer dependencies should be cleaned up when a layer is removed to avoid memory leaks. Recently, a JFXPanel was added into one of these layers. However, I’m seeing that even when all references are cleared, the fx nodes and its dependencies can remain through ViewPainter.ROOT_PATHS.

        See the 'Steps to Reproduce' and 'Source code for an executable test case' for an example.

        Note: This was originally posted as a question to the fx mailing list and it was suggested I report a ticket. See my original question here: https://mail.openjdk.java.net/pipermail/openjfx-dev/2020-December/028344.html

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1. Compile and run the 'Source code for an executable test case'
        2. Resize the panel
        3. Close the panel
        4. Take a heap dump.

        In the heap dump, you can see that the Dependency object held in the Node (NodeWithDependency) is eventually referenced by the static ViewPainter.ROOT_PATHS array. This reference seems to be linked through the properties of the Text node.

        This can take a few attempts to reproduce but I assume that's related to how the panel is resized.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        The dependencies are cleaned up or maybe there is a mechanism for client code to request a cleanup?
        ACTUAL -
        References remain in the ViewPainter.ROOT_NODES until they are pushed out by other fx rendering

        ---------- BEGIN SOURCE ----------
        import java.util.concurrent.CountDownLatch;
        import javafx.application.Platform;
        import javafx.embed.swing.JFXPanel;
        import javafx.scene.Scene;
        import javafx.scene.control.ScrollPane;
        import javafx.scene.layout.StackPane;
        import javafx.scene.text.Text;
        import javafx.scene.text.TextFlow;
        import javax.swing.JFrame;
        import javax.swing.SwingUtilities;

        import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;

        public class JFXPanelCleanup {

            public static void main(String[] args) {
                SwingUtilities.invokeLater(JFXPanelCleanup::initAndShowGUI);
                startKeepAliveThread();
            }

            private static void initAndShowGUI() {
                JFrame frame = new JFrame();
                JFXPanel jfxPanel = new CleaningFxPanel();
                frame.setContentPane(jfxPanel);
                frame.setSize(200, 100);
                frame.setVisible(true);
                frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);

                Platform.runLater(() -> jfxPanel.setScene(new Scene(new
        NodeWithDependency(new Dependency()))));
            }

            private static void startKeepAliveThread() {
                new Thread(() -> {
                    while (true) {
                        try {
                            //Force gc for good measure
                            System.gc();
                            Thread.sleep(3_000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }).start();
            }

            private static class CleaningFxPanel extends JFXPanel {

                @Override
                public void removeNotify() {
                    //Set the scene to null so that the invisible JFrame does not
        reference the scene
                    invokeAndWaitOnPlatform(() -> setScene(null));
                    super.removeNotify();
                }

                private void invokeAndWaitOnPlatform(Runnable runnable) {
                    CountDownLatch latch = new CountDownLatch(1);
                    Platform.runLater(() -> {
                        runnable.run();
                        latch.countDown();
                    });
                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }

            public static class NodeWithDependency extends StackPane {

                private final Dependency dataDependecy;

                public NodeWithDependency(Dependency dependency) {
                    this.dataDependecy = dependency;
                    getChildren().add(new ScrollPane(new TextFlow(new Text("The
        quick brown fox jumps over the lazy dog"))));
                }
            }

            public static class Dependency {

            }
        }
        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        Set 'prism.dirtyopts' to false.

        This mitigates the problem by not using the paths. However, my impression of this property is that it should only ever be used for debugging and will cause performance degradation.

        FREQUENCY : always


              kcr Kevin Rushforth
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

                Created:
                Updated:
                Resolved: