I have written an N-Body-simulation JavaFX-program which displays the simulated bodies as spheres. Unfortunately I'm struggling with memory leaks.
I figured out that the memory allocated for the spheres is not freed when the spheres are deleted from their container even if NO references (at least from my code) to the spheres exist.
It is easy to reproduce the behavior: The following code is essentially the JavaFX code generated automatically by Eclipse when a JavaFX project is created. I added a button and a group (which serves as container for the spheres). Clicking the button calls the method createSpheres which adds with each click 500 spheres to the container and which displays the used (heap) memory in the console.
package application;
import java.util.Random;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Sphere;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
// --- User defined code -----------------
VBox root = new VBox();
Button btn = new Button("Add spheres...");
Group container = new Group();
root.getChildren().addAll(btn, container);
btn.setOnAction(e -> {
createSpheres(container);
});
// ---------------------------------------
Scene scene = new Scene(root,400,400);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
// --- User defined code ------------------------------------------------------------------------
// Each call increases the used memory although the container is cleared and the GC is triggered.
// The problem does not occur when all spheres have the same radius.
// ----------------------------------------------------------------------------------------------
private void createSpheres(Group container) {
container.getChildren().clear();
Runtime.getRuntime().gc();
Random random = new Random();
for (int i = 0; i < 500; i++) {
//double d = 100; // OK
double d = 100 * random.nextDouble() + 1; // Problem
container.getChildren().add(new Sphere(d));
}
System.out.printf("Spheres added. Total number of spheres: %d. Used memory: %d Bytes of %d Bytes.\n",
container.getChildren().size(),
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().maxMemory());
}
// ----------------------------------------------------------------------------------------------
}
When I start the program the used memory increases with each click although the container is cleared with container.getChildren().clear() at the beginning of the method (as expected, the call deletes the spheres of the previous button clicks BUT the used memory increases further). The following call of Runtime.getRuntime().gc() is also ineffective (as expected, the gc is started BUT the memory is not freed). It seems as if the spheres are still referenced from somewhere or if resources are not disposed, presumably within the JavaFX code.
The output of the program is shown below: I used a maximum JVM heap of 4GB (-Xmx4g). After approximately 30 clicks the maximum heap size is reached and the application crashes with an out-of-memory exception. Also shown is the Visual VM output directly before the adding of the spheres and after the exception was thrown. The latter shows an almost full "old generation" pool. The last figure shows the heap during the adding of the spheres. Although the GC performed 106 collections no memory was freed.
Visual VM: Heap before the spheres are added
Visual VM: Heap after the spheres are added and the out-of-memory-exception was thrown
Visual VM: Heap during the adding of the spheres
It is noteworthy that the behavior depends on the radius of the spheres. In the example code each sphere has a pseudo-random radius. When the SAME radius is used for ALL spheres (independent from the value), e.g. new Sphere(100), the used memory stays constant, i.e. the memory is freed in a proper way! But the more spheres with DIFFERENT radius the more memory is consumed by the application. The following picture shows the memory when the spheres have identical radii. The memory is freed.
Visual VM: Heap during the adding of the spheres in the case of identical radii
I use JDK-9.0.1/JRE-9.0.1 and Eclipse Oxygen.1a Release (4.7.1a), Build id: 20171005-1200 and also JRE1.8.0_151 and Eclipse Neon.3 Release (4.6.3), Build id: 20170314-1500. My OS is Windows 7 Ultimate, 64-Bit.
Has anybody an idea how I can fix the problem (if possible at all) or is it actually a JavaFX memory leak issue?