JDK-8089776 : WebView memory leak when JavaScript callbacks are assigned.
  • Type: Bug
  • Component: javafx
  • Sub-Component: web
  • Affected Version: 8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2013-12-02
  • Updated: 2018-09-06
  • Resolved: 2016-03-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdResolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
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();
         }
      }
   }
   
} 
Comments
I tested this issue with patch "http://cr.openjdk.java.net/~mbilla/8089681/webrev.04/" ( JDK-8089681) and observed that this issue is fixed with the mentioned webrev. Hence closing this issue as a duplicate of JDK-8089681.
02-03-2016

This is a duplicate of JDK-8089681.
02-03-2016