JDK-8284352 : JLightweigthFrame is not cleared when SwingNode.setContent() is called
  • Type: Bug
  • Component: javafx
  • Sub-Component: swing
  • Affected Version: 8
  • Priority: P4
  • Status: Resolved
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2022-03-31
  • Updated: 2022-04-05
  • Resolved: 2022-04-05
Related Reports
Duplicate :  
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



Comments
Duplicate of JDK-8262518
05-04-2022