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

Removing TabPane with strong referenced content causes memory leak from weak one

    XMLWordPrintable

Details

    • x86_64
    • windows_10

    Description

      ADDITIONAL SYSTEM INFORMATION :
      Microsoft Windows [Version 10.0.17134.1040]
      openjdk version "13" 2019-09-17
      OpenJDK Runtime Environment AdoptOpenJDK (build 13+33)
      OpenJDK 64-Bit Server VM AdoptOpenJDK (build 13+33, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      Clearing TabPane from tabs and removing it from scene graph does not causes it to be removed from the memory, because "headersRegion" which is a StackPane in TabHeaderArea still keeps nodes in Parent#viewOrderChildren.

      This might be more global issue to some components which are not relayouted after inner element's removal.
      I have checked also TabPane without closing animation (setStyle("-fx-close-tab-animation: NONE;")), because it might been causing the bug, but it wasn't.

      Issue is NOT replicable on Java 8 - same test case might be applied, just comment out lines that does not compile and run.

      There's a workaround provided in test case which refreshes "headersRegion"'s Parent#viewOrderChildren list.

      REGRESSION : Last worked in version 8u231

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Open application from provided source code.
      2. Respectively press "RunGC", "Check local ref" button.
      3. Press "Remove tabs with parent" button.
      4. Repeat 2. step.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      After 2. TabPane is visible at the bottom of window. The label presents "Textfield ref is present", because text field is visible in second tab, so weak reference is not empty.
      After 3. TabPane disappears from window (and scenegraph).
      After 4. The label should present "Empty ref" text, because object kept in weak reference was removed from memory.
      ACTUAL -
      Same as "Expected Result" except "After 4.": reference is still kept in memory, so the label presents "Textfield ref is present" no matter how many times GC is cleared.

      ---------- BEGIN SOURCE ----------

      import java.lang.ref.WeakReference;

      import com.sun.javafx.scene.NodeHelper;
      import javafx.application.Application;
      import javafx.geometry.Insets;
      import javafx.scene.Node;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.Label;
      import javafx.scene.control.Tab;
      import javafx.scene.control.TabPane;
      import javafx.scene.control.TextField;
      import javafx.scene.layout.BorderPane;
      import javafx.scene.layout.HBox;
      import javafx.stage.Stage;

      public class TabPaneHeadersRegionIssue
      {

          public static void main( String[] args )
          {
              System.err.println( Runtime.version().toString() );
              Application.launch( MainFx.class, args );
          }

          public static class MainFx extends Application
          {

              private TextField textFieldStrongRef;

              @Override
              public void start( final Stage primaryStage ) throws Exception
              {
                  textFieldStrongRef = new TextField( "sample text" );
                  final Tab tab = createTab( textFieldStrongRef, "Tab with strong ref" );
                  final TextField tf = new TextField( "sample text local" );
                  final WeakReference< Node > localTextFieldRef = new WeakReference<>( tf );
                  final Tab tab2 = createTab( tf, "Tab with local (weak) ref" );
                  final TabPane tabPane = new TabPane( tab, tab2 );
                  tabPane.setStyle( "-fx-open-tab-animation: NONE;-fx-close-tab-animation: NONE;" );
                  final Label label = new Label();
                  updateText( label, localTextFieldRef );
                  final Button checkRefButton = createCheckRefButton( localTextFieldRef, label );

                  final Button tabRemoveButton = new Button( "Remove tabs with parent" );
                  final HBox hBox = new HBox( 5, label, checkRefButton, createRunGcButton(), tabRemoveButton,
                      createForceRemoveButton( tabPane ) );
                  final BorderPane borderPane = new BorderPane( hBox );

                  final WeakReference< TabPane > tabPaneRef = new WeakReference<>( tabPane );
                  tabRemoveButton.setOnAction( $ -> {
                      final TabPane t = tabPaneRef.get();
                      if( t == null )
                      {
                          return;
                      }
                      tabRemoveButton.setDisable( true );
                      removeTabWithParent( borderPane, t );
                  } );
                  borderPane.setBottom( tabPane );
                  borderPane.setPadding( new Insets( 5 ) );
                  Scene scene = new Scene( borderPane, 800, 600 );
                  primaryStage.setScene( scene );
                  primaryStage.show();
              }

              private static Tab createTab( final TextField aTextFieldStrongRef, final String aTabName )
              {
                  final Tab tab = new Tab( aTabName, aTextFieldStrongRef );
                  tab.setClosable( false );
                  return tab;
              }

              private static Button createForceRemoveButton( final TabPane aTabPane )
              {
                  final WeakReference< TabPane > tabPaneRef = new WeakReference<>( aTabPane );
                  final Button forceRemove = new Button( "Force remove" );
                  forceRemove.setOnAction( $ -> forceRemove( tabPaneRef ) );
                  return forceRemove;
              }

              private static Button createRunGcButton()
              {
                  final Button runGC = new Button( "RunGC" );
                  runGC.setOnAction( $ -> System.gc() );
                  return runGC;
              }

              private static Button createCheckRefButton( final WeakReference< Node > aRef, final Label aLabel )
              {
                  final Button button = new Button( "Check local ref" );
                  button.setOnAction( $ -> updateText( aLabel, aRef ) );
                  return button;
              }

              private static void forceRemove( final WeakReference< TabPane > aTabRef )
              {
                  final TabPane t = aTabRef.get();
                  if( t == null )
                  {
                      return;
                  }
                  final Node headersRegion = t.lookup( ".tab-header-area > .headers-region" );
                  NodeHelper.updatePeer( headersRegion );
              }

              private static void removeTabWithParent( final BorderPane aBorderPane, final TabPane tabPane )
              {
                  tabPane.getTabs().clear();
                  aBorderPane.getChildren().remove( tabPane );
              }

              private static void updateText( final Label aLabel, final WeakReference< Node > aRef )
              {
                  final boolean isEmpty = aRef.get() == null;
                  aLabel.setText( isEmpty ? "Empty ref" : "Textfield ref is present" );
              }

          }

      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Workaround is included in test case code - pressing "Force remove" button would cause "headersRegion" to be called Parent#doUpdatePeer() which calls Parent#computeViewOrderChildren(), so the list is cleared up and text field from weak reference is no longer kept in memory.

      FREQUENCY : always


      Attachments

        Issue Links

          Activity

            People

              arapte Ambarish Rapte
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: