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

JLightweigthFrame is not cleared when SwingNode.setContent() is called

    XMLWordPrintable

Details

    • generic
    • generic

    Description

      A DESCRIPTION OF THE PROBLEM :
      FULL PRODUCT VERSION :
      java version "8"
      Java(TM) SE Runtime Environment (build 8+271)
      Java HotSpot(TM) 64-Bit Server VM (build 8+271, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Reproduced on Windows 10, Ubuntu 20.04, Centos7.9

      A DESCRIPTION OF THE PROBLEM :
      When the content of a SwingNode is set to something else, the JLightweightFrame is never properly cleared.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Any code that dynamically replaces SwingNode existing content

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The JLightweightFrames will eventually be collected
      ACTUAL -
      The JLightweightFrames are not collected, causing massive memory leak in some uses case where the swing content of the SwingNode changes a lot.

      REPRODUCIBILITY :
      This bug can be reproduced in many ways.

      ---------- BEGIN SOURCE ----------
      package com.example;

      import javafx.application.Application;
      import javafx.embed.swing.SwingNode;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      import javax.swing.*;

      public class Main extends Application {

          SwingNode m_swingNode;
          Button m_btnChangeSwingNodeContent;
          int m_changeCounter;

          public static void main(String[] args) {
              launch(args);
          }

          @Override
          public void start(Stage primaryStage) {
              primaryStage.setTitle("SwingNode JLightweightFrame Leak Demo");
              
              m_swingNode = new SwingNode();
              m_changeCounter = 0;
              m_btnChangeSwingNodeContent = new Button("CHANGE SWINGNODE CONTENT");

              m_btnChangeSwingNodeContent.setOnAction(new EventHandler<ActionEvent>() {

                  @Override
                  public void handle(ActionEvent event) {
                      m_changeCounter = m_changeCounter + 1;
                      JButton btn = new JButton("TEST BUTTON NB "+ m_changeCounter);
                      JPanel panel = new JPanel();
                      panel.add(btn);

                      m_swingNode.setContent(null); //Removing previous content if any
                      m_swingNode.setContent(panel);
                  }
              });

              StackPane root = new StackPane();
              root.getChildren().add(m_swingNode);
              root.getChildren().add(m_btnChangeSwingNodeContent);
              primaryStage.setScene(new Scene(root, 300, 250));
              primaryStage.show();
          }
      }
      ---------- END SOURCE ----------

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      You can reproduce it using this source code below:

      package com.example;

      import javafx.application.Application;
      import javafx.embed.swing.SwingNode;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      import javax.swing.*;

      public class Main extends Application {

          SwingNode m_swingNode;
          Button m_btnChangeSwingNodeContent;
          int m_changeCounter;

          public static void main(String[] args) {
              launch(args);
          }

          @Override
          public void start(Stage primaryStage) {
              primaryStage.setTitle("SwingNode JLightweightFrame Leak Demo");
              
              m_swingNode = new SwingNode();
              m_changeCounter = 0;
              m_btnChangeSwingNodeContent = new Button("CHANGE SWINGNODE CONTENT");

              m_btnChangeSwingNodeContent.setOnAction(new EventHandler<ActionEvent>() {

                  @Override
                  public void handle(ActionEvent event) {
                      m_changeCounter = m_changeCounter + 1;
                      JButton btn = new JButton("TEST BUTTON NB "+ m_changeCounter);
                      JPanel panel = new JPanel();
                      panel.add(btn);

                      m_swingNode.setContent(null); //Removing previous content if any
                      m_swingNode.setContent(panel);
                  }
              });

              StackPane root = new StackPane();
              root.getChildren().add(m_swingNode);
              root.getChildren().add(m_btnChangeSwingNodeContent);
              primaryStage.setScene(new Scene(root, 300, 250));
              primaryStage.show();
          }
      }

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      When pressing the button "CHANGE SWINGNODE CONTENT" a new JButton shall appear with the following text "TEST BUTTON NB X" X being the number of times the button was clicked.

      When tracking memory using JVisualVM, JLightweightFrame content should be equal to 1, the number of still displayed JLightweightFrame.
      ACTUAL -
      When pressing the button "CHANGE SWINGNODE CONTENT" a new JButton shall appear with the following text "TEST BUTTON NB X" X being the number of times the button was clicked.

      When tracking memory using JVisualVM, JLightweightFrame content don't decrease when garbage collector is called.

      ---------- BEGIN SOURCE ----------
      package com.example;

      import javafx.application.Application;
      import javafx.embed.swing.SwingNode;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      import javax.swing.*;

      public class Main extends Application {

          SwingNode m_swingNode;
          Button m_btnChangeSwingNodeContent;
          int m_changeCounter;

          public static void main(String[] args) {
              launch(args);
          }

          @Override
          public void start(Stage primaryStage) {
              primaryStage.setTitle("SwingNode JLightweightFrame Leak Demo");
              
              m_swingNode = new SwingNode();
              m_changeCounter = 0;
              m_btnChangeSwingNodeContent = new Button("CHANGE SWINGNODE CONTENT");

              m_btnChangeSwingNodeContent.setOnAction(new EventHandler<ActionEvent>() {

                  @Override
                  public void handle(ActionEvent event) {
                      m_changeCounter = m_changeCounter + 1;
                      JButton btn = new JButton("TEST BUTTON NB "+ m_changeCounter);
                      JPanel panel = new JPanel();
                      panel.add(btn);

                      m_swingNode.setContent(null); //Removing previous content if any
                      m_swingNode.setContent(panel);
                  }
              });

              StackPane root = new StackPane();
              root.getChildren().add(m_swingNode);
              root.getChildren().add(m_btnChangeSwingNodeContent);
              primaryStage.setScene(new Scene(root, 300, 250));
              primaryStage.show();
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The problem is due to the JLightweightFrame is never released by de SwingNodeDisposer.

      The SwingNodeDisposer holds an hard pointer to the JLightweightFrame that prevents its suppression.

      I have modified the setContentImpl() function to use a WeakReference<JLightweightFrame> instead of an hard pointer and now memory is properly released.

      ...
                  WeakReference<JLightweightFrame> lwFramePtr = new WeakReference<JLightweightFrame>(lwFrame);
                  SwingNodeDisposer disposeRec = new SwingNodeDisposer(lwFramePtr);
                  Disposer.addRecord(this, disposeRec);
      ...

      Contact me if you want the full SwingNode class or any additionnal details

      FREQUENCY : always


      Attachments

        1. Capture_1.PNG
          Capture_1.PNG
          213 kB
        2. Capture_2.PNG
          Capture_2.PNG
          242 kB
        3. Main.java
          2 kB

        Issue Links

          Activity

            People

              pnarayanaswa Praveen Narayanaswamy
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: