[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    JFXPanel cleanup of ViewPainter.ROOT_PATHS
From:       Brendan DalCin <brendandalcin () gmail ! com>
Date:       2020-12-11 22:24:02
Message-ID: CAFHLQVTwWCnZb=vtsHsCwHV4CiPgfmUEJM+6dt8ExagCbDS6hg () mail ! gmail ! com
[Download RAW message or body]

Hi there,

I've recently been having some trouble with cleanup of FX resources in a
JFXPanel inside a Swing application. I'm hoping you can point me in the
right direction.

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. This is with JavaFX 15. From debugging I've found
that in the quantum toolkit, the ViewPainter.ROOT_PATHS still holds
references. Oddly I've only ever been able to see the problem when a Text
node is involved.

The example at the bottom of this email shows the problem. To reproduce:
launch the application, resize the panel, close the panel and 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. Note also that the
scene in the JFXPanel is null'd out to ensure the Swing frame does not keep
a reference to the fx scene.

One workaround I found is to 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.

So with all that said, I'm wondering if you could explain what I'm doing
wrong here and/or if there is a more appropriate way to cleanup the
JFXPanel in this scenario.

Thanks in advance for any help.

Brendan

P.s. I attempted to post this last week without being subscribed to the
mailing list. The message seems to be stuck awaiting moderator approval. My
apologies if this shows up as a duplicate post.

---

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 {

    }
}
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic