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

WebView memory leak when JavaScript callbacks are assigned.

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P4 P4
    • tbd
    • 8, 9
    • javafx
    • Windows and Linux Java 1.8.0ea b117

    • web
    • Verified

      I have noticed that when a Java callback is set on the JavaScript window object within a WebView, the view will not be gc'd unless the callback class is static and has no outside references. This situation is easier to demonstrate than fully describe.

      Please run the provided test case and use the 2 top buttons to open and then close 4 windows (2 with each button). After ensuring that these stages are closed, click on the "Print References" button and watch the console.

      The console will show that 2 of the stages were gc'd and 2 were not. The 2 that won't collect are the ones where the callback has a reference to the stage it is in. Since nothing else has a strong reference to that stage it should be able to be collected.

      I have noticed that if you use JSObject.removeMember() for the callback then everything will get GC'd. If this is necessary because of the way the WebEngine operates then the docs should be updated to mention it's a necessity.

      +++++++++++++++++++++ Test Class +++++++++++++++++++++++++++++++

      import java.lang.ref.WeakReference;
      import java.util.HashSet;
      import java.util.Set;

      import netscape.javascript.JSObject;
      import javafx.application.Application;
      import javafx.event.ActionEvent;
      import javafx.event.EventHandler;
      import javafx.geometry.Pos;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.layout.HBox;
      import javafx.scene.layout.Region;
      import javafx.scene.layout.StackPane;
      import javafx.scene.layout.VBox;
      import javafx.scene.web.WebEngine;
      import javafx.scene.web.WebView;
      import javafx.stage.Stage;

      public class WebViewTest extends Application {
         
         @Override public void start(final Stage primaryStage) throws Exception {

            primaryStage.centerOnScreen();
            primaryStage.setHeight(350);
            primaryStage.setWidth(500);
            
            final Set<WeakReference<Object>> refs = new HashSet<>();
            Button showButton = new Button("New View (will collect)...");
            showButton.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  Stage newStage = createNewView(false);
                  newStage.show();
                  refs.add( new WeakReference<Object>(newStage) );
               }
            });
            Button showButton2 = new Button("New View (won't collect)...");
            showButton2.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  Stage newStage = createNewView(true);
                  newStage.show();
                  refs.add( new WeakReference<Object>(newStage) );
               }
            });
            
            Button printButton = new Button("Print References");
            printButton.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent event) {
                  Runtime.getRuntime().gc();
                  Set<WeakReference<Object>> collected = new HashSet<>();
                  for ( WeakReference<Object> ref: refs ) {
                     if ( ref.get() == null ) {
                        collected.add(ref);
                     }
                  }
                  refs.removeAll(collected);
                  System.out.println("*********************************************");
                  System.out.println(String.format("%d objects collected.",
                        collected.size()));
                  System.out.println(String.format("%d objects remain.",
                        refs.size()));
                  System.out.println("*********************************************");
               }
            });
            
            VBox box = new VBox(20, new HBox(10, showButton, showButton2), printButton );
            box.setMaxHeight(Region.USE_PREF_SIZE);
            box.setMaxWidth(Region.USE_PREF_SIZE);
            box.setAlignment(Pos.TOP_CENTER);
            primaryStage.setScene(new Scene( new StackPane(box) ));
            
            primaryStage.show();

         }
         
         private Stage createNewView(boolean backReference) {
            Stage stage = new Stage();
            
            WebView webview = new WebView();
            WebEngine engine = webview.getEngine();
            engine.load("http://google.com");
            JSObject window = (JSObject) engine.executeScript("window");
            window.setMember("callback", new Callback(backReference ? stage: null));
            stage.setScene(new Scene(webview, 800, 600));
            
            return stage;
         }
         
         public static void main(String[] args) throws Exception {
            launch(args);
         }

         // example javascript callback
         public static class Callback {
            
            private final Stage stageReference_;
            
            public Callback( Stage stageReference ) {
               stageReference_ = stageReference;
            }
            
            // example callback method
            public void exit() {
               if ( stageReference_ != null ) {
                  stageReference_.close();
               }
            }
         }
         
      }

            mbilla Murali Billa
            csmithjfx Charles Smith (Inactive)
            Votes:
            1 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported: