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

SwingNode.setContent does not close previous content, resulting in memory leak

XMLWordPrintable

    • b07
    • x86_64
    • windows_10

      ADDITIONAL SYSTEM INFORMATION :
      OS Windows 10

      Tested on:
      * OpenJDK/OpenJFX 8.0.271-b08
      * Liberica JDK/JFX combined build 15.0.1+b01
      * OpenJDK JDK 17 early access build 10 with OpenJFX 16 build 7

      A DESCRIPTION OF THE PROBLEM :
      When setting the content of a SwingNode, the old content is not garbage collected.

      There are some other bugs that appear related, but are marked as resolved and this is definitely not resolved. I'm not sure if this is truly a separate bug, or if one of those bugs should be reopened. Here are the related bugs:
      * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8093738
      * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8241972
      * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8218124

      So far my debugging (using JDK 1.8.0.271) has shown the references to old content are kept alive in a static list of windows in the GlassStage class. The should be removed when close() is called on the JComponent, but this doesn't seem to be called via SwingNode.setContent().

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the following program listed in the "source code for an executable test case" section.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Should print that there are 1 or 2 panels in memory. Possibly a few more but the number should not trend upward over time.
      ACTUAL -
      Prints:

      Panels in memory: 0
      Panels in memory: 1
      Panels in memory: 2
      Panels in memory: 3
      Panels in memory: 4
      Panels in memory: 5
      Panels in memory: 6
      Panels in memory: 7
      Panels in memory: 8
      ...

      And goes on forever (or I suppose until it runs out of memory, but I didn't wait that long).

      ---------- BEGIN SOURCE ----------
      import java.lang.ref.WeakReference;
      import java.util.Collection;
      import java.util.concurrent.CopyOnWriteArrayList;

      import javax.swing.JPanel;
      import javax.swing.SwingUtilities;

      import javafx.application.Application;
      import javafx.embed.swing.SwingNode;
      import javafx.scene.Scene;
      import javafx.scene.layout.Pane;
      import javafx.stage.Stage;


      public class LeakDemo extends Application {

          //Keep week references to all panels that we've ever generated to see if any
          //of them get collected.
          private Collection<WeakReference<JPanel>> panels = new CopyOnWriteArrayList<>();
          
          @Override
          public void start(Stage primaryStage) throws Exception {
              
              SwingNode node = new SwingNode();
              
              Pane root = new Pane();
              root.getChildren().add(node);
              
              //Kick off a thread that repeatedly creates new JPanels and resets the swing node's content
              new Thread(() -> {
                  
                  while(true) {
                      
                      //Lets throw in a little sleep so we can read the output
                      try {
                          Thread.sleep(500);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      
                      SwingUtilities.invokeLater(() -> {
                          JPanel panel = new JPanel();
                          panels.add(new WeakReference<>(panel));
                          node.setContent(panel);
                      });
                      
                      System.out.println("Panels in memory: " + panels.stream().filter(ref -> ref.get() != null).count());
                      
                      //I know this doesn't guarantee anything, but prompting a GC gives me more confidence that this
                      //truly is a bug.
                      System.gc();
                  }
                  
              }).start();
              
              primaryStage.setScene(new Scene(root, 100, 100));
              
              primaryStage.show();
              
          }
          
          public static void main(String[] args) {
              launch(args);
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      If you place a JComponent (such as a JPanel) as the content of the swing node, and then update the content of that JComponent rather than setting the SwingNode's content directly, it will collect the old content appropriately. See https://stackoverflow.com/questions/66270491/content-of-swingnode-not-garbage-collected-when-content-changed

      FREQUENCY : always


            psadhukhan Prasanta Sadhukhan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: