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

Memory Leak in Tooltip if content changes while tooltip closed

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P4
    • Resolution: Cannot Reproduce
    • Affects Version/s: openjfx11
    • Fix Version/s: None
    • Component/s: javafx
    • Labels:
    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      windows_10

      Description

      ADDITIONAL SYSTEM INFORMATION :
      Java fx 13 and openjdk 11. OS: Windows(tho that seems not related)

      A DESCRIPTION OF THE PROBLEM :
      We have a pretty big java fx application:
      https://github.com/FAForever/downlords-faf-client
      It has a list of open games. If you hover over it it shows u additional info about that game in a tooltip. This tooltip gets updated quite often or better whenever something changes in the game. There is one tooltip for all games only containing ui for the last one the user hovered over. But the tooltip leaks and leaked ui can reach huge amounts of memory. The memory is released as soon as u hover over the games list again.

      After heap dump analysis it came out that dirtyNodes Array in Scene.class hold huge amount of memory. After further investigation it turned out that the Scene unregisters itself from the pulse and does not render elements if the Tooltip gets hidden. See disposePeer Method in Scene and windowPropertyImpl Method in Scene.

      The dirtyNodes Array is not cleared (ever) if the tooltip is hidden and causes a memory leak keeping dead controls alive by saving them from gc.

      See similar issue or potentally releated:
      https://bugs.openjdk.java.net/browse/JDK-8113169

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Make a tooltip that updates controls even if it is closed...

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The dirty nodes list gets cleared even if the tooltip is open or it does not keep elements from being gced.
      ACTUAL -
      DirtyNodes List growth and leaks elements.

      ---------- BEGIN SOURCE ----------
      package com.faforever;

      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.scene.Scene;
      import javafx.scene.control.Label;
      import javafx.scene.control.Tooltip;
      import javafx.scene.layout.Pane;
      import javafx.stage.Stage;

      import java.lang.reflect.Field;
      import java.lang.reflect.Method;

      /**
       * What to do?
       * Hover once over the tooltip and see the size of dirty nodes list.
       * The number of dirty nodes will stay small as long as u hover over it, but as soon as u leave the label and the tooltip closes it will increase statically.
       * Unless u hover again it will keep increasing by 2 per second.
       * The are some hacks presented how to stop this....
       * Tho a real change in java fx logic to prevent this would be preferred.
       */
      public class HalloFx extends Application {

          @Override
          public void start(Stage stage) throws Exception {

              String fix = getParameters().getNamed().get("fix");
              int fixNumber = fix== null || fix.equals("") ? 0:Integer.parseInt(fix);

              Scene scene = new Scene(new Pane());
              Label tooltip_here = new Label("tooltip here");
              Tooltip tooltip = new Tooltip();
              Pane tooltipPane = new Pane();
              tooltip.setGraphic(tooltipPane);

              new Thread(()->{
                  while (true){
                      Platform.runLater(()->{
                          tooltipPane.getChildren().clear();
                          Label yet_another_label = new Label("yet another label");
                          if(fixNumber==1 && tooltip.isShowing()){
                              tooltipPane.getChildren().add(yet_another_label);
                              //Well one way to fix it is just to stop updating the UI or to stop changing it when tooltip is not showing
                          }

                          Scene tooltipScene = tooltipPane.getScene();
                          if(fixNumber==2){
                              try {
                                  Field scenePulseListener = Scene.class.getDeclaredField("scenePulseListener");
                                  scenePulseListener.setAccessible(true);
                                  Object pulseListener = scenePulseListener.get(tooltipScene);
                                  Method synchronizeSceneNodes = pulseListener.getClass().getDeclaredMethod("synchronizeSceneNodes");
                                  synchronizeSceneNodes.setAccessible(true);
                                  synchronizeSceneNodes.invoke(pulseListener);
                                  //Keep up the pulse of the tooltip when it is closed, because the tooltip stops listening to the pulse when closed. This is a second way to fix it.
                              } catch (Exception e) {
                                  e.printStackTrace();
                              }
                          }

                          if(tooltipScene==null) return;

                          try {
                              Field dirtyNodesSize = Scene.class.getDeclaredField("dirtyNodesSize");
                              dirtyNodesSize.setAccessible(true);
                              Object size = dirtyNodesSize.get(tooltipScene);
                              if(size!=null)System.out.println(size);
                          } catch (Exception e) {
                              e.printStackTrace();
                          }


                      });
                      try {
                          Thread.sleep(500);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }).start();
              tooltip_here.setTooltip(tooltip);
              scene.setRoot(new Pane(tooltip_here));

              stage.setTitle("JavaFX Tooltip leak");
              stage.setScene(scene);
              stage.show();
          }

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

      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Well see source code

      FREQUENCY : always


        Attachments

          Activity

            People

            • Assignee:
              arapte Ambarish Rapte
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: