S.T.K S.T.K - 5 months ago 103
Java Question

JavaFX WeakReference memory leak when drawing charts

I encountered a memory leak I just can't fix in my code.
Scenario: The user opens up a new window, in which a chart is drawn (in this case a LineChart). But when the window is closed, java.lang.ref.WeakReference and javafx.beans.property.BooleanPropertyBase$Listener remain in memory. Their number perfectly corresponds to the drawn data points (XYChart.Data).
I just can't figure out how to get rid of them.
In my code, many of these windows are opened and closed frequently, with 10k-100k data points per chart and memory fills up quite quickly.

I'm sure I made some stupid mistake, but I just can't find it. Help would be greatly appreciated!

Sample Code:

import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Test extends Application {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(Test.class, args);
}

@Override
public void start(final Stage primaryStage) {
primaryStage.setTitle("Hello World");
Group root = new Group();
Scene scene = new Scene(root, 300, 250, Color.LIGHTGREEN);
Button btn = new Button();
btn.setLayoutX(100);
btn.setLayoutY(80);
btn.setText("Create stage");
btn.setOnAction(new EventHandler<ActionEvent>() {

public void handle(ActionEvent event) {
new CreateStage();
primaryStage.toFront();

}
});
root.getChildren().add(btn);
primaryStage.setScene(scene);
primaryStage.show();
}
}

class CreateStage {

public CreateStage() {
Stage stage = new Stage();
stage.setTitle("Line Chart Sample");
//Basic Chart attributes
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("RT [minutes]");
yAxis.setLabel("Intensity");
LineChart<Number, Number> linechart = new LineChart(xAxis, yAxis);

XYChart.Series newSeries = new XYChart.Series();

List<XYChart.Data> list = new ArrayList<>();
//just fill the chart with data points
for (int j = 0; j < 10000; j++) {
float intensity = j;
float currentRT = j;

list.add(new XYChart.Data(currentRT, intensity));
}
newSeries.getData().addAll(list);

// add new Series
linechart.getData().add(newSeries);

Scene scene = new Scene(linechart,800,600);


stage.setScene(scene);
stage.show();
}
}

Answer

I was able to solve the problem. Calling

series.getData().clear();

on every XYChart.Series when closing the window get's rid of the memory leak. Things like javafx.scene.layout.CornerRadii, com.sun.javafx.sg.prism.NGRegion and javafx.scene.layout.Background still remain in memory, but get garbage collected eventually and don't build up.

Here's the fixed code:

import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Test extends Application {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Application.launch(Test.class, args);
    }

    @Override
    public void start(final Stage primaryStage) {
        primaryStage.setTitle("Hello World");
        Group root = new Group();
        Scene scene = new Scene(root, 300, 250, Color.LIGHTGREEN);
        Button btn = new Button();
        btn.setLayoutX(100);
        btn.setLayoutY(80);
        btn.setText("Create stage");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            public void handle(ActionEvent event) {
                CreateStage();
                primaryStage.toFront();

            }
        });
        root.getChildren().add(btn);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public void CreateStage() {

        Stage stage = new Stage();
        stage.setTitle("Line Chart Sample");
        //Basic Chart attributes
        NumberAxis xAxis = new NumberAxis();
        NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("RT [minutes]");
        yAxis.setLabel("Intensity");

        //linechart.getData().clear();
        LineChart<Number, Number> linechart = new LineChart(xAxis, yAxis);

        XYChart.Series newSeries = new XYChart.Series();

        List<XYChart.Data> list = new ArrayList<>();
        //just fill the chart with data points
        for (int j = 0; j < 10000; j++) {
            float intensity = j;
            float currentRT = j;

            list.add(new XYChart.Data(currentRT, intensity));
        }
        newSeries.getData().addAll(list);

        // add new Series
        linechart.getData().add(newSeries);

        Scene scene = new Scene(linechart, 800, 600);

        stage.setScene(scene);
        stage.show();

        //this fixes it
        stage.setOnCloseRequest(event -> {
            for (XYChart.Series series : linechart.getData()) {
                series.getData().clear();
            }

        });

    }
}

Thanks to @fabian for pointing me into the right direction.

Comments