javafx - 在 JavaFX 3D 中删除对象时出现内存泄漏

标签 javafx memory-leaks javafx-3d

我编写了一个 N-Body-simulation JavaFX 程序,它将模拟的 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 代码中。

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

Program console output

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

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

Visual VM: Heap during the adding of the spheres in the case of identical radii

我使用 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编号:20170314-1500。我的操作系统是 Windows 7 Ultimate,64 位。

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

最佳答案

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

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

对于球体,此键基于它们的半径和分割数:

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 将始终包含一个元素。

这对于有多个具有完全相同网格的球体的常规情况非常方便:您不必重新生成这些网格,只需执行一次并缓存网格。

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

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

通过反射,我设法监控 sphereCache 的大小同时添加 500 个球体直到达到内存异常:
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();
    }
    ...

}

可能您想提交问题 here .

关于javafx - 在 JavaFX 3D 中删除对象时出现内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47518745/

相关文章:

memory-leaks - Knockout.js 绑定(bind)在 Ajax 驱动的页面上

JavaFX 动画/带 Pane 的 3d View

Javafx 3D 在分割 Pane 中消失

javafx - 是否可以在 javafx 中制作线性渐变的百分比?

memory-leaks - jemalloc 堆分析仅跟踪分配吗?

JavaFX8 : How to shut down anti-aliasing on text?

c++ - 重载 operator new 而不重载 operator delete

java - 如何使用 JavaFX、JCSG 和 FXyz 进行差分运算后获得闭合的 3D 网格?

java - 在 Eclipse 中使用 JavaFX

JavaFX 可搜索组合框(如 js select2)