Я написал JavaFX-программу моделирования N-Body, которая отображает моделируемые тела в виде сфер. К сожалению, я борюсь с утечками памяти.
Я понял, что память, выделенная для сфер, не освобождается, когда сферы удаляются из их контейнера, даже если НЕТ ссылок (по крайней мере, из моего кода) на сферы.
Это поведение легко воспроизвести: следующий код представляет собой код JavaFX, автоматически сгенерированный Eclipse при создании проекта JavaFX. Я добавил кнопку и группу (которая служит контейнером для сфер). Нажатие на кнопку вызывает метод createSpheres, который добавляет с каждым щелчком 500 сфер в контейнер и отображает в консоли используемую (кучу) память.
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());
}
// ----------------------------------------------------------------------------------------------
}
Когда я запускаю программу, используемая память увеличивается с каждым щелчком, хотя контейнер очищается с помощью container.getChildren().clear() в начале метода (как и ожидалось, вызов удаляет сферы предыдущих нажатий кнопки, НО использованная память увеличивается еще больше). Следующий вызов Runtime.getRuntime().gc() также неэффективен (как и ожидалось, gc запускается, НО память не освобождается). Кажется, что на сферы все еще ссылаются откуда-то или если ресурсы не распоряжаются, предположительно, в коде JavaFX.
Вывод программы показан ниже: Я использовал максимальную кучу JVM 4 ГБ (-Xmx4g). Приблизительно после 30 щелчков достигается максимальный размер кучи, и приложение аварийно завершает работу с исключением нехватки памяти. Также показаны выходные данные Visual VM непосредственно перед добавлением сфер и после создания исключения. Последний показывает почти полный пул «старого поколения». На последнем рисунке показана куча во время добавления сфер. Хотя сборщик мусора выполнил 106 сборов, память не была освобождена.
Visual VM: куча перед добавлением сфер
Visual VM: куча после добавления сфер и возникновения исключения нехватки памяти а>
Visual VM: Heap во время добавления сфер
Примечательно, что поведение зависит от радиуса сфер. В примере кода каждая сфера имеет псевдослучайный радиус. Когда ОДИН и тот же радиус используется для ВСЕХ сфер (независимо от значения), например. new Sphere(100), используемая память остается неизменной, т.е. память освобождается должным образом! Но чем больше сфер с РАЗНЫМ радиусом, тем больше памяти потребляет приложение. На следующем рисунке показана память, когда сферы имеют одинаковые радиусы. Память освобождается.
Visual VM: Куча при добавлении сфер в случае одинаковых радиусов
Я использую JDK-9.0.1/JRE-9.0.1 и выпуск Eclipse Oxygen.1a (4.7.1a), идентификатор сборки: 20171005-1200, а также JRE1.8.0_151 и выпуск Eclipse Neon.3 (4.6.3), сборку идентификатор: 20170314-1500. Моя ОС — Windows 7 Максимальная, 64-разрядная.
Кто-нибудь знает, как я могу решить эту проблему (если это вообще возможно), или это действительно проблема с утечкой памяти JavaFX?