JNI 库在垃圾回收时释放内存



我正在使用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()(但它的使用绝不限于在本机中创建的对象)。

另一方面,本机对象不受垃圾回收的影响。

这里有两种情况。

  1. 本机代码分配 Java 对象。这些对象与所有其他 Java 对象一样是 GC。如果本机搞砸并持有强引用,它可以阻止 GC。
  2. 本机
  3. 代码分配本机内存。总理事会对此一无所知;由图书馆安排释放它。一种方法是拥有一个带有终结器的 Java 对象,该终结器进行必要的 JNI 调用以释放本机内存。

相关内容

  • 没有找到相关文章

最新更新