Memory leak in JavaFX indefinite Timeline

2019-06-07 06:00发布

问题:

I'm designing a stopwatch using JavaFX. The code runs well. Except for enormous cumulative memory leaks over time. The leak increases whenever I increase the Timeline's framerate. I'm currently on Ubuntu 16.04 with 4gigs of RAM, and the leak is happening at a speed of 300MB/min at 30fps. That's 5MBps. I can understand that this may happen due to the repetitive drawing over the Scene, but why would it be cumulative? Shouldn't the JVM take care of this?

Main.java :

package UI;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        primaryStage.setTitle("StopWatch");
        primaryStage.setScene(new Scene(getPane(), 400, 400));
        primaryStage.show();
    }


    private BorderPane getPane(){
        BorderPane pane = new BorderPane();

        ClockUI clockUI = new ClockUI();
        clockUI.setMinSize(200,200);
        pane.setCenter(clockUI);

        ButtonBar buttonBar = new ButtonBar();
        Button startButton = new Button("Start");
        startButton.setOnAction(e->clockUI.startClock());
        Button pauseButton = new Button("Stop");
        pauseButton.setOnAction(e->clockUI.stopClock());
        Button resetButton = new Button("Reset");
        resetButton.setOnAction(e->clockUI.resetClock());
        buttonBar.getButtons().addAll(startButton, pauseButton, resetButton);
        pane.setBottom(buttonBar);

        return pane;
    }

    public static void main(String[] args) {
        System.setProperty("prism.lcdtext","false");
        launch(args);
    }
}

ClockUI.java :

package UI;

import javafx.animation.*;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;

/**
 * Created by subhranil on 23/6/17.
 */
public class ClockUI extends StackPane {

    private final Rotate hourRotate;
    private final Rotate minuteRotate;
    private final Rotate secondRotate;
    private final Timeline hourTimeline;
    private final Timeline minuteTimeline;
    private final Timeline secondTimeline;
    private final ParallelTransition clockTransition;

    public ClockUI() {
        super();

        Line hourHand = getHand(80, Color.WHITE);
        hourRotate = getRotate(hourHand);
        hourTimeline = createRotateTimeline(Duration.hours(12), hourRotate);

        Line minuteHand = getHand(100, Color.WHITE);
        minuteRotate = getRotate(minuteHand);
        minuteTimeline = createRotateTimeline(Duration.minutes(60), minuteRotate);

        Line secondHand = getHand(90, Color.WHITE);
        secondRotate = getRotate(secondHand);
        secondTimeline = createRotateTimeline(Duration.seconds(60), secondRotate);

        clockTransition = new ParallelTransition(hourTimeline, minuteTimeline, secondTimeline);

        Circle back = new Circle(120);
        back.centerXProperty().bind(widthProperty().divide(2));
        back.centerYProperty().bind(heightProperty().divide(2));
        back.setStyle("-fx-fill: #555555");
        setStyle("-fx-background-color: #333333;");

        getChildren().addAll(back, hourHand, minuteHand, secondHand);
    }

    private Timeline createRotateTimeline(Duration duration, Rotate rotate) {
        Timeline timeline = new Timeline(30);
        timeline.getKeyFrames().add(new KeyFrame(duration, new KeyValue(rotate.angleProperty(), 360)));
        timeline.setCycleCount(Animation.INDEFINITE);
        return timeline;
    }

    public void startClock() {
        if (clockTransition.getStatus() != Animation.Status.RUNNING) {
            clockTransition.play();
        }
    }

    public void stopClock() {
        if (clockTransition.getStatus() == Animation.Status.RUNNING) {
            clockTransition.pause();
        }
    }

    public void resetClock() {
        stopClock();
        clockTransition.stop();
    }

    private Rotate getRotate(Line line){
        Rotate r = new Rotate(0);
        r.pivotXProperty().bind(line.startXProperty());
        r.pivotYProperty().bind(line.startYProperty());
        line.getTransforms().add(r);
        return r;
    }

    private Line getHand(int size, Paint color) {
        Line hand = new Line();
        hand.startXProperty().bind(widthProperty().divide(2));
        hand.startYProperty().bind(heightProperty().divide(2));
        hand.endXProperty().bind(widthProperty().divide(2));
        hand.endYProperty().bind(heightProperty().divide(2).subtract(size));
        hand.setStroke(color);
        hand.setStrokeWidth(3);

        return hand;
    }

}

INFO : I've tried various other methods, like running an ExecutorService, using Task and Thread, but all yield same results.

回答1:

Try this and see if you are having the same problem.

ClockGUI

import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;

/**
 *
 * @author Sedrick
 */
public class ClockGUI {

    Circle clockFace;
    Line second;
    Line minute;
    Line hour;
    Rotate secondRotation;
    Rotate minuteRotation;
    Rotate hourRotation;
    AnchorPane currentClockFace;

    public ClockGUI()
    {
        currentClockFace = new AnchorPane();
        currentClockFace.setPrefSize(100, 100);

        clockFace = new Circle(100 / 2, 100 / 2, 100 / 2);
        clockFace.setStroke(Color.BLACK);
        clockFace.setFill(Color.TRANSPARENT);

        second = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 40);
        secondRotation = new Rotate();
        secondRotation.pivotXProperty().bind(second.startXProperty());
        secondRotation.pivotYProperty().bind(second.startYProperty());
        second.getTransforms().add(secondRotation);

        minute = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 30);
        minuteRotation = new Rotate();
        minuteRotation.pivotXProperty().bind(minute.startXProperty());
        minuteRotation.pivotYProperty().bind(minute.startYProperty());
        minute.getTransforms().add(minuteRotation);

        hour = new Line(100 / 2, 100 / 2, 100 / 2, 100 / 2 - 20);
        hourRotation = new Rotate();
        hourRotation.pivotXProperty().bind(hour.startXProperty());
        hourRotation.pivotYProperty().bind(hour.startYProperty());
        hour.getTransforms().add(hourRotation);

        currentClockFace.getChildren().addAll(clockFace, second, minute, hour);

    }

    public AnchorPane getCurrentClock()
    {
        return currentClockFace;
    }

    public void rotateSecondLine()
    {
        secondRotation.setAngle(secondRotation.getAngle() + 6);
    }

    public double getRotateSecondLine()
    {
        return secondRotation.getAngle();
    }

    public void setRotateSecond(double degree)
    {
        secondRotation.setAngle(degree);
    }

    public void rotateMinuteLine()
    {
        minuteRotation.setAngle(minuteRotation.getAngle() + 6);
    }

    public double getRotateMinuteLine()
    {
        return minuteRotation.getAngle();
    }

    public void setRotateMinute(double degree)
    {
        minuteRotation.setAngle(degree);
    }

    public void rotateHourLine()
    {
        hourRotation.setAngle(hourRotation.getAngle() + 6);
    }

    public double getRotateHourLine()
    {
        return hourRotation.getAngle();
    }

    public void setRotateHour(double degree)
    {
        hourRotation.setAngle(degree);
    }
}

Main

import javafx.animation.*;
import javafx.application.*;
import javafx.event.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import javafx.util.*;

/**
 *
 * @author Sedrick
 */
public class JavaFXApplication54 extends Application {

    @Override
    public void start(Stage primaryStage)
    {
        VBox root = new VBox();
        ClockGUI cgui = new ClockGUI();

        StackPane stackpane = new StackPane();
        stackpane.getChildren().add(cgui.getCurrentClock());
        root.getChildren().add(stackpane);

        Button btn = new Button("Rotate seconds");
        btn.setOnAction((event) -> {
            cgui.rotateSecondLine();
        });

        Button btn2 = new Button("Rotate minutes");
        btn2.setOnAction((event) -> {
            cgui.rotateMinuteLine();
        });

        Button btn3 = new Button("Rotate hours");
        btn3.setOnAction((event) -> {
            cgui.rotateHourLine();
        });

        root.getChildren().addAll(btn, btn2, btn3);
        Scene scene = new Scene(root, 300, 250);

        Timeline timeline = new Timeline();
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.getKeyFrames().add(
                new KeyFrame(Duration.seconds(1),
                        new EventHandler() {
                    // KeyFrame event handler
                    @Override
                    public void handle(Event event)
                    {
                        System.out.println(cgui.getRotateSecondLine());
                        cgui.rotateSecondLine();
                        if (cgui.getRotateSecondLine() >= 360) {
                            cgui.setRotateSecond(0);
                            cgui.rotateMinuteLine();
                        }
                        if (cgui.getRotateMinuteLine() >= 360) {
                            cgui.setRotateMinute(0);
                            cgui.rotateHourLine();
                        }
                        if (cgui.getRotateHourLine() >= 360) {
                            cgui.setRotateHour(0);
                        }
                    }
                }
                ));
        timeline.playFromStart();
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}