I've got a window in JavaFX that I need to 'clean' I.E: Reset the values of all of the fields and set it back to how it was when it was initialised. I figured I could do something like window.dispose()
, but it turns out nothing like that appears to exist.
This seems like a massive oversight in the window engine as I'm going to be opening multiple windows that I don't want to persist. If I do stage.close()
it just hides the window without releasing the memory, potentially leading to memory leaks.
When you call close()
, (or, equivalently, hide()
) on a window, the FX toolkit will release any references it holds to that window. Consequently, as long as you do not retain references to the window, once close()
has been called it will be eligible for garbage collection. It behaves like any other Java object with respect to garbage collection - once garbage collected, resources associated with the window are released.
Here's a demo that opens a new window every second, closing the previous window when it does so. The window displays an image in a label, so it consumes a reasonable chunk of memory. The primary stage displays memory statistics (also updating every second). This behaves exactly as you would expect, with the memory gradually increasing until the garbage collector kicks in, at which point the memory usage drops back. (On my system, it cycles between ~7MB and ~65MB; your mileage may vary depending on OS, JDK version, and system resources.) You can insert a call to System.gc();
at the end of the onSucceeded
handler (for demo purposes only; I do not recommend doing this in real code) if you want to see more clearly that garbage collection releases all the resources.
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ConstantlyOpenWindows extends Application {
@Override
public void start(Stage primaryStage) {
ScheduledService<?> service = periodicallyShowNewWindow();
Pane root = createMemoryMonitor();
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.show();
service.start();
}
private ScheduledService<Integer> periodicallyShowNewWindow() {
Screen screen = Screen.getPrimary();
double maxX = screen.getBounds().getMaxX();
AtomicInteger count = new AtomicInteger();
ObjectProperty<Stage> visibleStage = new SimpleObjectProperty<>();
ScheduledService<Integer> service = new ScheduledService<Integer>() {
@Override
protected Task<Integer> createTask() {
Task<Integer> task = new Task<Integer>() {
@Override
public Integer call() {
return count.incrementAndGet();
}
};
return task ;
}
};
service.setOnSucceeded(event -> {
Stage lastStage = visibleStage.get();
Stage stage = createWindowWithImage(service.getValue());
visibleStage.set(stage);
stage.setX(maxX - 480);
stage.show();
if (lastStage != null) {
lastStage.close();
}
// System.gc();
});
service.setPeriod(Duration.seconds(1));
return service;
}
private Stage createWindowWithImage(int count) {
Stage stage = new Stage();
ImageView image = createImage();
Label label = new Label("Window "+count);
label.setGraphic(image);
label.setContentDisplay(ContentDisplay.BOTTOM);
stage.setScene( new Scene(new StackPane(label), 480, 500) );
return stage;
}
private ImageView createImage() {
WritableImage img = new WritableImage(400, 400);
Random rng = new Random();
int x = rng.nextInt(40);
int y = rng.nextInt(40);
PixelWriter pw = img.getPixelWriter();
for (int i = 0; i < 400; i++) {
for (int j = 0 ; j < 400 ; j++) {
if (i >= x*10 && i < (x+1)*10 && j >= y*10 && j < (y+1) * 10) {
pw.setColor(i, j, Color.CORNFLOWERBLUE);
} else {
pw.setColor(i, j, Color.ANTIQUEWHITE);
}
}
}
return new ImageView(img);
}
private Pane createMemoryMonitor() {
LongProperty totalMemory = new SimpleLongProperty(Runtime.getRuntime().totalMemory());
LongProperty freeMemory = new SimpleLongProperty(Runtime.getRuntime().freeMemory());
LongProperty maxMemory = new SimpleLongProperty(Runtime.getRuntime().maxMemory());
NumberBinding usedMemory = totalMemory.subtract(freeMemory);
Label usedMemoryLabel = new Label();
usedMemoryLabel.textProperty().bind(usedMemory.asString("Used memory: %,d"));
Label freeMemoryLabel = new Label();
freeMemoryLabel.textProperty().bind(freeMemory.asString("Free memory: %,d"));
Label totalMemoryLabel = new Label();
totalMemoryLabel.textProperty().bind(totalMemory.asString("Total memory: %,d"));
Label maxMemoryLabel = new Label();
maxMemoryLabel.textProperty().bind(maxMemory.asString("Max memory: %,d"));
Series<Number, Number> series = new Series<>();
series.setName("Used memory");
AtomicInteger time = new AtomicInteger();
Timeline updateMemory = new Timeline(new KeyFrame(Duration.seconds(1), event -> {
totalMemory.set(Runtime.getRuntime().totalMemory());
freeMemory.set(Runtime.getRuntime().freeMemory());
maxMemory.set(Runtime.getRuntime().maxMemory());
series.getData().add(new Data<>(time.incrementAndGet(), usedMemory.getValue()));
if (series.getData().size() > 100) {
series.getData().subList(0, series.getData().size() - 100).clear();
}
}));
updateMemory.setCycleCount(Animation.INDEFINITE);
updateMemory.play();
VBox labels = new VBox(usedMemoryLabel, freeMemoryLabel, totalMemoryLabel, maxMemoryLabel);
labels.setAlignment(Pos.CENTER);
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setForceZeroInRange(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Memory");
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis);
chart.setAnimated(false);
chart.getData().add(series);
BorderPane root = new BorderPane(chart, labels, null, null, null);
return root;
}
public static void main(String[] args) {
launch(args);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With