我正在使用JCUDA,想知道JNI对象是否足够智能,可以在垃圾回收时解除分配?我可以理解为什么这可能不适用于所有情况,但我知道它在我的情况下都有效,所以我的后续问题是:我如何才能做到这一点?我可以设置"模式"吗?我需要构建一个抽象层吗?或者也许答案真的是"不,永远不要尝试",那为什么不呢?
编辑:我只指通过JNI创建的本机对象,而不是Java对象。我知道所有 Java 对象都得到平等的 W.R.T. 垃圾回收。
通常,此类库不会由于垃圾回收而释放内存。特别是: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 根本不需要回收这几个字节的内存。(这里省略了关于黑客解决方法的讨论,比如调用System.gc()
左右 - 底线是:它不起作用)。
所以回答你的实际问题:JCuda 是一个非常低级的库。这意味着您拥有全部权力,但也拥有手动内存管理的全部责任。我知道这是"不方便的"。当我开始创建 JCuda 时,我最初打算将其作为面向对象包装库的低级后端。但是,为像 CUDA 这样的复杂通用库创建一个健壮、稳定且普遍适用的抽象层具有挑战性,我不敢处理这样的项目 - 最后但并非最不重要的是因为......像垃圾收集这样的事情...
JNI 中创建的 Java 对象与所有其他 Java 对象相同,并且在时机成熟时会被垃圾回收和销毁。为了防止这些对象过早被销毁,我们经常使用 JNI 函数env->NewGlobalRef()
(但它的使用绝不限于在本机中创建的对象)。
另一方面,本机对象不受垃圾回收的影响。
这里有两种情况。
- 本机代码分配 Java 对象。这些对象与所有其他 Java 对象一样是 GC。如果本机搞砸并持有强引用,它可以阻止 GC。 本机
- 代码分配本机内存。总理事会对此一无所知;由图书馆安排释放它。一种方法是拥有一个带有终结器的 Java 对象,该终结器进行必要的 JNI 调用以释放本机内存。