我正在使用 JCUDA,想知道 JNI 对象是否足够聪明,可以在垃圾收集时释放它们?我可以理解为什么这可能不适用于所有情况,但我知道它会在我的情况下工作,所以我的后续问题是:我怎样才能做到这一点?有我可以设置的“模式”吗?我需要建立一个抽象层吗?或者也许答案真的是“不,永远不要尝试”,那为什么不呢?
编辑:我只指通过 JNI 创建的本机对象,而不是 Java 对象。我知道所有 Java 对象都被同等对待 WRT 垃圾回收。
我正在使用 JCUDA,想知道 JNI 对象是否足够聪明,可以在垃圾收集时释放它们?我可以理解为什么这可能不适用于所有情况,但我知道它会在我的情况下工作,所以我的后续问题是:我怎样才能做到这一点?有我可以设置的“模式”吗?我需要建立一个抽象层吗?或者也许答案真的是“不,永远不要尝试”,那为什么不呢?
编辑:我只指通过 JNI 创建的本机对象,而不是 Java 对象。我知道所有 Java 对象都被同等对待 WRT 垃圾回收。
在 JNI 中创建的 Java 对象等同于所有其他 Java 对象,并且在它们的时间到来时被垃圾收集和销毁。为了防止这些对象过早地被销毁,我们经常使用 JNI 函数env->NewGlobalRef()
(但它的使用绝不限于在本机中创建的对象)。
另一方面,本机对象不受垃圾回收的影响。
这里有两种情况。
通常,此类库不会因为垃圾回收而释放内存。特别是:JCuda 不这样做,并且没有可以这样做的选项或“模式”。
原因很简单:它不起作用。
你经常会有这样的模式:
void doSomethingWithJCuda()
{
CUdeviceptr data = new CUdeviceptr();
cuMemAlloc(data, 1000);
workWith(data);
// *(See notes below)
}
在这里,分配了本机内存,Java 对象充当该本机内存的“句柄”。
在最后一行,data
对象超出范围。因此,它有资格进行垃圾收集。但是,有两个问题:
1.垃圾收集器只会销毁 Java 对象,不会释放分配的内存cuMemAlloc
或任何其他本地调用。
因此,您通常必须通过显式调用来释放本机内存
cuMemFree(data);
在离开方法之前。
2.你不知道Java对象什么时候会被垃圾回收——或者它是否会被垃圾回收。
一个常见的误解是,当一个对象不再可访问时,它就会被垃圾回收,但这不一定是正确的。
正如bmargulies在他的回答中指出的那样:
一种方法是拥有一个带有终结器的 Java 对象,该终结器进行必要的 JNI 调用以释放本机内存。
finalize()
简单地覆盖这些“句柄”对象的方法并在cuMemFree(this)
那里进行调用可能看起来是一个可行的选择。例如,JavaCL(一个还允许将 GPU 与 Java 一起使用的库,因此在概念上有点类似于 JCuda)的作者已经尝试过这种方法。
但它根本不起作用:即使一个 Java 对象不再可访问,这并不意味着它会立即被垃圾回收。
您根本不知道何时finalize()
调用该方法。
这很容易导致严重错误:当您有 100 MB 的 GPU 内存时,您可以使用 10 个CUdeviceptr
对象,每个对象分配 10MB。您的 GPU 内存已满。但是对于Java来说,这几个CUdeviceptr
对象只占用了几个字节,finalize()
在应用程序运行时可能根本不会调用该方法,因为JVM根本不需要回收这几个字节的内存。(在这里省略关于 hacky 变通办法的讨论,比如打电话System.gc()
左右 - 底线是:它不起作用)。
所以回答你的实际问题:JCuda是一个非常低级的库。这意味着您拥有全部权力,同时也拥有手动内存管理的全部职责。我知道这很“不方便”。当我开始创建 JCuda 时,我最初打算将它作为面向对象包装库的低级后端。但是为像 CUDA 这样复杂的通用库创建一个健壮、稳定和普遍适用的抽象层是具有挑战性的,我不敢处理这样的项目——最后但并非最不重要的一点是,由于...事物所隐含的复杂性比如垃圾收集...