在JavaFX 3D中删除对象时内存泄漏 [英] Memory leaks when removing objects in JavaFX 3D

查看:212
本文介绍了在JavaFX 3D中删除对象时内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个N-Body仿真JavaFX程序,它将模拟物体显示为球体。不幸的是,我正在努力解决内存泄漏问题。

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.

重现行为很容易:下面的代码基本上是Eclipse创建JavaFX项目时自动生成的JavaFX代码。我添加了一个按钮和一个组(用作球体的容器)。单击该按钮会调用createSpheres方法,该方法会在每次单击500个球体时添加到容器中,并在控制台中显示已使用的(堆)内存。

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());
        }
        // ----------------------------------------------------------------------------------------------
    }

当我启动程序时,使用的内存会随着每个内存而增加单击尽管容器在方法开头用container.getChildren()。clear()清除(正如预期的那样,调用删除前一个按钮的球体点击但是使用的内存进一步增加)。以下调用Runtime.getRuntime()。gc()也无效(正如预期的那样,gc启动但内存未被释放)。看起来似乎仍然从某个地方引用了球体,或者是否未处理资源,可能是在JavaFX代码中。

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.

程序的输出如下所示:我使用的最大JVM堆为4GB(-Xmx4g)。大约30次单击后,达到最大堆大小,应用程序崩溃时出现内存不足异常。还显示了在添加球体之前和抛出异常之后的Visual VM输出。后者显示了几乎完整的老一代游泳池。最后一个图显示了添加球体时的堆。尽管GC执行了106个集合,但没有释放内存。

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:在球体之前堆积已添加

Visual VM:在添加球体并抛出内存不足异常后堆积

Visual VM:添加球体时的堆积物

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

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 :在相同半径的情况下添加球体时的堆

我使用JDK-9.0.1 / JRE-9.0.1和Eclipse Oxygen。 1a Release(4.7.1a),Build id:20171005-1200以及JRE1.8.0_151和Eclipse Neon.3 Release(4.6.3),Build id:20170314-1500。我的操作系统是Windows 7旗舰版,64位。

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.

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

Has anybody an idea how I can fix the problem (if possible at all) or is it actually a JavaFX memory leak issue?

推荐答案

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

The answer to your question can be found in a package protected class named javafx.scene.shape.PredefinedMeshManager.

每次创建 Sphere / Box / Cylinder 3D形状, TriangleMesh 对象被添加到 HashMap ,基于给定的密钥。

Every time you create an Sphere/Box/Cylinder 3D shape, the TriangleMesh object is added to a HashMap, based on a given key.

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

For the spheres, this key is based on their radius and number of divisions:

private static int generateKey(float r, int div) {
    int hash = 5;
    hash = 23 * hash + Float.floatToIntBits(r);
    hash = 23 * hash + div;
    return hash;
}

在测试中,你没有修改分割数,所以当你对所有500个球体使用相同的半径,你为它们生成相同的键,因此管理器hashMap将始终包含一个单独的元素。

In your test, you are not modifying the number of divisions, so when you use the same radius for all the 500 spheres, you are generating the same key for all of them, so the manager hashMap will contain always one single element.

这很漂亮方便的情况下你有几个球具有完全相同的网格:你不必再生成那些网格,你只需要做一次并缓存网格。

This is pretty convenient for the regular case where you have several spheres with exact same mesh: you don't have to generate all over again those meshes, you do it just once and cache the mesh.

相反,如果球体的半径不同,则键将始终不同,每次单击时,您将向hashMap添加500个新对象。

On the contrary, if you have different radius for the spheres, the key will be always different, and on every click you will be adding 500 new objects to the hashMap.

当您使用球体清理容器时,管理器不知道这一点并且不会从hashMap中删除它们,因此计数会增加,直到您获得内存异常。

While you clean the container with the spheres, the manager doesn't know about that and doesn't remove them from the hashMap, therefore the count increases until you get the memory exception.

通过反射,我设法监控 sphereCache 的大小,同时添加500个球体,直到达到记忆异常:

With reflection, I've managed to monitor the size of the sphereCache while adding 500 spheres until reaching the memory exception:

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)

显然,如果我们有权访问它缓存我们可以防止这种内存泄漏:

Obviously, if we had access to that cache we could prevent this memory leak:

private void createSpheres(Group container) {
    container.getChildren().clear();
    if (sphereCache != null) {
        sphereCache.clear();
    }
    ...

}

您可能希望在此处提交问题。

Probably you will want to file an issue here.

这篇关于在JavaFX 3D中删除对象时内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆