在JavaFX 3D中删除对象时内存泄漏

在JavaFX 3D中删除对象时内存泄漏,javafx,memory-leaks,javafx-3d,Javafx,Memory Leaks,Javafx 3d,我已经编写了一个N-Body-simulation JavaFX程序,它将模拟的物体显示为球体。不幸的是,我正在与内存泄漏作斗争 我发现,当球体从容器中删除时,即使不存在对球体的引用(至少从我的代码中),也不会释放为球体分配的内存 复制这种行为很容易:下面的代码本质上是Eclipse在创建JavaFX项目时自动生成的JavaFX代码。我添加了一个按钮和一个组(作为球体的容器)。单击该按钮将调用createSpheres方法,该方法会在每次单击时将500个球体添加到容器中,并在控制台中显示已使用(

我已经编写了一个N-Body-simulation JavaFX程序,它将模拟的物体显示为球体。不幸的是,我正在与内存泄漏作斗争

我发现,当球体从容器中删除时,即使不存在对球体的引用(至少从我的代码中),也不会释放为球体分配的内存

复制这种行为很容易:下面的代码本质上是Eclipse在创建JavaFX项目时自动生成的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());
        }
        // ----------------------------------------------------------------------------------------------
    }
包应用;
导入java.util.Random;
导入javafx.application.application;
导入javafx.stage.stage;
导入javafx.scene.Group;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.layout.VBox;
导入javafx.scene.shape.Sphere;
公共类主扩展应用程序{
@凌驾
公共无效开始(阶段primaryStage){
试一试{
//---用户定义代码-----------------
VBox root=新的VBox();
按钮btn=新按钮(“添加球体…”);
组容器=新组();
root.getChildren().addAll(btn,容器);
btn.设置动作(e->{
创建球体(容器);
});
// ---------------------------------------
场景=新场景(根,400400);
初级阶段。场景(场景);
primaryStage.show();
}捕获(例外e){
e、 printStackTrace();
}
}
公共静态void main(字符串[]args){
发射(args);
}
//---用户定义代码------------------------------------------------------------------------
//尽管容器被清除并且GC被触发,但每次调用都会增加已用内存。
//如果所有球体的半径相同,则不会出现此问题。
// ----------------------------------------------------------------------------------------------
专用void createSpheres(组容器){
container.getChildren().clear();
Runtime.getRuntime().gc();
随机=新随机();
对于(int i=0;i<500;i++){
//双d=100;//确定
双d=100*random.nextDouble()+1;//问题
container.getChildren().add(新球体(d));
}
System.out.printf(“添加的球体。球体总数:%d。使用的内存:%d字节,共%d字节。\n”,
container.getChildren().size(),
Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().maxMemory());
}
// ----------------------------------------------------------------------------------------------
}
当我启动程序时,使用的内存会随着每次单击而增加,尽管在方法的开头使用container.getChildren().clear()清除了容器(正如预期的那样,调用会删除前面按钮单击的球体,但使用的内存会进一步增加)。以下对Runtime.getRuntime().gc()的调用也无效(正如预期的那样,gc已启动,但内存未释放)。似乎球体仍然在某个地方被引用,或者资源没有被释放,可能是在JavaFX代码中

该程序的输出如下所示:我使用了4GB(-Xmx4g)的最大JVM堆。单击大约30次后,达到最大堆大小,应用程序崩溃,出现内存不足异常。还显示了添加球体之前和抛出异常之后的可视VM输出。后者显示了几乎全部的“老一代”人才库。最后一个图显示了添加球体期间的堆。尽管GC执行了106次收集,但没有释放内存

值得注意的是,行为取决于球体的半径。在示例代码中,每个球体都有一个伪随机半径。当所有球体(独立于该值)使用相同半径时,例如新球体(100),使用的内存保持不变,即以适当的方式释放内存!但不同半径的球体越多,应用程序消耗的内存就越多。下图显示了球体具有相同半径时的内存。内存被释放

我使用JDK-9.0.1/JRE-9.0.1和Eclipse Oxygen.1a版本(4.7.1a),构建id:20171005-1200,还使用JRE1.8.0_151和Eclipse Neon.3版本(4.6.3),构建id:20170314-1500。我的操作系统是Windows 7 Ultimate,64位


有人知道我如何解决这个问题(如果可能的话)或者它实际上是一个JavaFX内存泄漏问题吗?

您的问题的答案可以在名为
JavaFX.scene.shape.PredefinedMeshManager的包保护类中找到

每次创建
球体
/
长方体
/
圆柱体
三维形状时,
三角形网格
对象将基于给定的关键点添加到
哈希映射

对于球体,此关键点基于其半径和分割数:

private static int generateKey(float r, int div) {
    int hash = 5;
    hash = 23 * hash + Float.floatToIntBits(r);
    hash = 23 * hash + div;
    return hash;
}
在测试中,您没有修改分割数,因此当您对所有500个球体使用相同的半径时,您将为所有球体生成相同的键,因此manager hashMap将始终包含一个元素

这对于有几个球面的普通情况来说非常方便
Spheres added. Total number of spheres: 500. Used memory: 7794744 Bytes of 3817865216 Bytes.
sphereCache: javafx.scene.shape.PredefinedMeshManager@cb26fc7 Size: 500

Spheres added. Total number of spheres: 500. Used memory: 147193720 Bytes of 3817865216 Bytes.
sphereCache: javafx.scene.shape.PredefinedMeshManager@cb26fc7 Size: 1000

...

Spheres added. Total number of spheres: 500. Used memory: 3022528400 Bytes of 3817865216 Bytes.
sphereCache: javafx.scene.shape.PredefinedMeshManager@cb26fc7 Size: 11497

Spheres added. Total number of spheres: 500. Used memory: 3158410200 Bytes of 3817865216 Bytes.
sphereCache: javafx.scene.shape.PredefinedMeshManager@cb26fc7 Size: 11996

Spheres added. Total number of spheres: 500. Used memory: 3292418936 Bytes of 3817865216 Bytes.
sphereCache: javafx.scene.shape.PredefinedMeshManager@cb26fc7 Size: 12185

Exception in thread "JavaFX Application Thread"  java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3284)
at com.sun.javafx.collections.ObservableIntegerArrayImpl.ensureCapacity(ObservableIntegerArrayImpl.java:254)
at com.sun.javafx.collections.ObservableIntegerArrayImpl.setAllInternal(ObservableIntegerArrayImpl.java:131)
at com.sun.javafx.collections.ObservableIntegerArrayImpl.setAll(ObservableIntegerArrayImpl.java:156)
at javafx.scene.shape.Sphere.createMesh(Sphere.java:420)
at javafx.scene.shape.PredefinedMeshManager.getSphereMesh(PredefinedMeshManager.java:68)
at javafx.scene.shape.Sphere.impl_updatePeer(Sphere.java:157)
at javafx.scene.Node.impl_syncPeer(Node.java:503)
at javafx.scene.Scene$ScenePulseListener.synchronizeSceneNodes(Scene.java:2290)
private void createSpheres(Group container) {
    container.getChildren().clear();
    if (sphereCache != null) {
        sphereCache.clear();
    }
    ...