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

Passing objects between JavaScript (JavaFX / WebKit) and Java causes a memory leak

    Details

      Backports

        Description

        FULL PRODUCT VERSION :
        java version "1.8.0_92"
        Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
        Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows [Version 6.1.7601]

        A DESCRIPTION OF THE PROBLEM :
        We have a JavaFX application that passes some data between Java and JavaScript, we have noticed that this application's process memory was increasing and eventually crashing. After running a JVM memory profiler we couldn't identify the problem with Java, but we were able to pinpoint the problem to Webkit. It seems that there is a memory leak outside of JVM's memory space.

        There are 2 cases: one is passing data from Java to JavaScript and the second is from JavaScript to Java. For this bug, we will focus on the case from Java to JavaScript.

        We're passing the data as strings (JSON in our app) and it seems that overtime something isn't getting released.

        The test has 2 payloads: a java string and a char array. The java string tests leaks, the char array isn't.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Run MemoryLeak2.java.
        Click on "Start Simulation" button
        Watch the process' memory in the task manager.



        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        The process' memory shouldn't increase.
        ACTUAL -
        The process' memory was increasing non stop.


        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------
        test.html
        ======

        <!DOCTYPE HTML>
        <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
        </head>
        <body>
        <div style="position:relative">
        <h2>Controls</h2>
        <button onclick="simulateRealTimeUpdates()">Start simulation</button>
        <button onclick="stopRealtime()">Stop simulation</button>
        <button onclick="openNewWindow()">Open new window</button>
        </div>

        <script>
            function simulateRealTimeUpdates() {
                window.service.data({
                    callback: function(dataStr) {
                        dataStr = null;
                    }
                });
            }

            function openNewWindow() {
                window.open('grid.html');
            }

            function stopRealtime() {
                window.service.cancelTimer();
            }
        </script>
        </body>
        </html>

        MemoryLeak2Service.java
        =====================

        package memoryLeak2;

        import javafx.application.Platform;
        import netscape.javascript.JSObject;

        import java.util.Arrays;
        import java.util.Timer;
        import java.util.TimerTask;

        public class MemoryLeak2Service {
            private final Timer timer = new java.util.Timer();
            private final int size = 1000000;
            private final String strBuffer;
            //private final char[] chrBuffer;
            
            public MemoryLeak2Service()
            {
             StringBuffer buffer = new StringBuffer(size);
             for (int i = 0; i < size; i++){
             buffer.append("A");
              }
            
             strBuffer = buffer.toString();
             //chrBuffer = strBuffer.toCharArray();
            }
            
            public void data(JSObject cb) {
                TimerTask tt = new TimerTask() {
                    public void run() {
                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                             // strBuffer - LEAKS, chrBuffer isn't leaking.
                             cb.call("callback", strBuffer);
                             //cb.call("callback", chrBuffer);
                            }
                        });
                    }
                };
                timer.schedule(tt, 1000, 100);
            }

            /**
             * Cancel timer
             */
            public void cancelTimer() {
                timer.cancel();
                timer.purge();

            }
        }

        MemoryLeak2.java
        ===============

        package memoryLeak2;

        import javafx.application.Application;
        import javafx.beans.value.ChangeListener;
        import javafx.beans.value.ObservableValue;
        import javafx.concurrent.Worker.State;
        import javafx.event.EventHandler;
        import javafx.geometry.HPos;
        import javafx.geometry.VPos;
        import javafx.scene.Scene;
        import javafx.scene.layout.Region;
        import javafx.scene.paint.Color;
        import javafx.scene.web.PopupFeatures;
        import javafx.scene.web.WebEngine;
        import javafx.scene.web.WebEvent;
        import javafx.scene.web.WebView;
        import javafx.stage.Stage;
        import javafx.stage.WindowEvent;
        import javafx.util.Callback;
        import netscape.javascript.JSObject;

        public class MemoryLeak2 extends Application {

            public static final int WIDTH = 1000;
            public static final int HEIGHT = 600;

            @Override
            public void start(Stage stage) {
                create(stage);
            }

            public static void create(Stage stage) {
                // create the scene
                stage.setTitle("Original Web View");
                Scene scene = new Scene(new Browser(), WIDTH, HEIGHT, Color.web("#666970"));
                stage.setScene(scene);
                stage.show();


                stage.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, new EventHandler<WindowEvent>() {

                    @Override
                    public void handle(WindowEvent event) {
                        //Exit the application on first window close
                        System.exit(0);
                    }

                });



            }

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

        }

        class Browser extends Region {

            /**
             * html file to load
             */
            private String fileToLoad = "test.html";
            final WebView browser = new WebView();
            final WebEngine webEngine = browser.getEngine();
            private MemoryLeak2Service myService;

            public Browser() {
                //apply the styles
                getStyleClass().add("browser");



                webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {

                    @Override
                    public void changed(ObservableValue<? extends State> stateParam, State oldState, State newState) {

                        switch (newState) {
                        case READY:
                            break;
                        case SCHEDULED:
                            break;

                        case RUNNING:
                            break;

                        case CANCELLED:
                            break;

                        case SUCCEEDED:
                            if(!"about:blank".equals(webEngine.getLocation())) {
                                final JSObject window = (JSObject) webEngine.executeScript("window");
                                myService = new MemoryLeak2Service();
                                //set service as widow member
                                window.setMember("service", myService);
                                window.call("init");
                            }
                            break;

                        default:
                            break;
                        }
                    }
                });

                //alert to display some javascript logs
                webEngine.setOnAlert(new EventHandler<WebEvent<String>>() {

                    @Override
                    public void handle(WebEvent<String> webEvent) {
                        System.out.println(webEvent.getData());
                    }

                });

                // load the web page
                webEngine.load(Browser.class.getResource(fileToLoad).toExternalForm());

                getChildren().add(browser);
                webEngine.setCreatePopupHandler(new Callback<PopupFeatures, WebEngine>() {
                    @Override
                    public WebEngine call(PopupFeatures param) {
                        Stage stg = new Stage();
                        stg.setTitle("Popup View");
                        final Browser root = new Browser();
                        Scene scene = new Scene(root, 800, 800, Color.web("#666970"));
                        stg.setScene(scene);
                        stg.show();
                        stg.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, new EventHandler<WindowEvent>() {

                            @Override
                            public void handle(WindowEvent event) {
                                if(root != null) {
                                    //On close cancel te timer
                                    root.getMyService().cancelTimer();
                                    //load null to free some memory
                                    root.getWebEngine().load(null);
                                }
                            }

                        });

                        return root.getWebEngine();
                    }
                });
            }

            public WebEngine getWebEngine() {
                return webEngine;
            }

            public MemoryLeak2Service getMyService() {
                return myService;
            }

            @Override protected void layoutChildren() {
                double w = getWidth();
                double h = getHeight();
                layoutInArea(browser,0,0,w,h,0, HPos.CENTER, VPos.CENTER);
            }

            @Override protected double computePrefWidth(double height) {
                return MemoryLeak2.HEIGHT;
            }

            @Override protected double computePrefHeight(double width) {
                return MemoryLeak2.WIDTH;
            }
        }


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

        CUSTOMER SUBMITTED WORKAROUND :
        Use char array instead:

        cb.call("callback", chrBuffer);

        This will not cause a memory leak.


          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  mbilla Murali Billa
                  Reporter:
                  webbuggrp Webbug Group
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  6 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: