我编写了一个N-Body仿真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());
}
// ----------------------------------------------------------------------------------------------
}
当我启动程序时,使用的内存会随着每次单击而增加,尽管容器在方法开头用container.getChildren()。clear()清除(正如预期的那样,调用会删除前一个按钮点击的范围但是使用的内存会进一步增加)。以下调用Runtime.getRuntime()。gc()也无效(正如预期的那样,gc启动但内存未被释放)。看起来似乎仍然从某个地方引用了球体,或者是否未处理资源,可能是在JavaFX代码中。
程序的输出如下所示:我使用了4GB(-Xmx4g)的最大JVM堆。大约30次单击后,达到最大堆大小,应用程序崩溃时出现内存不足异常。还显示了在添加球体之前和抛出异常之后的Visual VM输出。后者显示了一个几乎完整的老一代&#34;池。最后一个图显示了添加球体时的堆。尽管GC执行了106个集合,但没有释放内存。
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半径用于所有球体时(独立于该值),例如,新的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) ),构建ID:20170314-1500。我的操作系统是Windows 7旗舰版,64位。
有没有人知道如何解决问题(如果可能的话)或者它实际上是JavaFX内存泄漏问题?
答案 0 :(得分:2)
您的问题的答案可以在名为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。