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

Memory leak when switching ButtonSkin

XMLWordPrintable

    • x86_64
    • windows_7

      ADDITIONAL SYSTEM INFORMATION :
      * OpenJDK 11.0.2
      * Redhat 6.8 and Windows 7 64bit. Confident this is agnostic of OS

      A DESCRIPTION OF THE PROBLEM :
      When the skin of a control is changed the old skin is not garbage collected.

      I've traced this to strong reference in an event listener

      Specifically com.sun.javafx.scene.control.skin.BehaviorSkinBase adds a listener for context menu request in its constructor. i.e. this line

      control.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler);

      However this listener is never removed in dispose()

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the test application
      Monitor number of skin instances with JVisualVM or other profiler

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Only single instance of skin object, unused instances should be garbage collected
      ACTUAL -
      Constant increase in number of skin objects (i.e. they're not being garbage collected)

      ---------- BEGIN SOURCE ----------
      import com.sun.javafx.scene.control.skin.ButtonSkin;

      import javafx.animation.Animation;
      import javafx.animation.KeyFrame;
      import javafx.animation.Timeline;
      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.stage.Stage;
      import javafx.util.Duration;

      public class SkinMemoryLeak extends Application {

          public static class ButtonSkinCustom1 extends ButtonSkin {
              public ButtonSkinCustom1(Button button) {
                  super(button);
              }
          }

          public static class ButtonSkinCustom2 extends ButtonSkin {
              public ButtonSkinCustom2(Button button) {
                  super(button);
              }
          }

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

          @Override
          public void start(Stage primaryStage) throws Exception {
              Button button = new Button("Foo");
              Scene scene = new Scene(button);
              primaryStage.setScene(scene);
              primaryStage.show();
              Timeline animation = new Timeline();
              animation.getKeyFrames().add(new KeyFrame(Duration.seconds(1), event -> {
                  if (button.getSkin() instanceof ButtonSkinCustom2) {
                      button.setSkin(new ButtonSkinCustom1(button));
                  } else {
                      button.setSkin(new ButtonSkinCustom2(button));
                  }
              }));
              animation.setCycleCount(Animation.INDEFINITE);
              animation.play();
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Can be monkey patched by following in affected skin

      @Override
      public void dispose() {
          try {
              Field field = BehaviorSkinBase.class.getDeclaredField("contextMenuHandler");
              field.setAccessible(true);
              EventHandler handler = (EventHandler) field.get(this);
              getSkinnable().removeEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, handler);
          } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
              e.printStackTrace();
          }
          super.dispose();
      }

      FREQUENCY : always


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

              Created:
              Updated:
              Resolved: