0

我正在使用 JCUDA,想知道 JNI 对象是否足够聪明,可以在垃圾收集时释放它们?我可以理解为什么这可能不适用于所有情况,但我知道它会在我的情况下工作,所以我的后续问题是:我怎样才能做到这一点?有我可以设置的“模式”吗?我需要建立一个抽象层吗?或者也许答案真的是“不,永远不要尝试”,那为什么不呢?

编辑:我只指通过 JNI 创建的本机对象,而不是 Java 对象。我知道所有 Java 对象都被同等对待 WRT 垃圾回收。

4

3 回答 3

2

在 JNI 中创建的 Java 对象等同于所有其他 Java 对象,并且在它们的时间到来时被垃圾收集和销毁。为了防止这些对象过早地被销毁,我们经常使用 JNI 函数env->NewGlobalRef()(但它的使用绝不限于在本机中创建的对象)。

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

于 2015-03-14T19:31:27.553 回答
2

这里有两种情况。

  1. 本机代码分配 Java 对象。这些对象与所有其他 Java 对象一样是 GC。如果本机出错并持有强引用,它可以防止 GC。
  2. 本机代码分配本机内存。GC 对此一无所知;由图书馆安排释放它。一种方法是拥有一个带有终结器的 Java 对象,该终结器进行必要的 JNI 调用以释放本机内存。
于 2015-03-14T19:36:51.840 回答
2

通常,此类库不会因为垃圾回收而释放内存。特别是: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 这样复杂的通用库创建一个健壮、稳定和普遍适用的抽象层是具有挑战性的,我不敢处理这样的项目——最后但并非最不重要的一点是,由于...事物所隐含的复杂性比如垃圾收集...

于 2015-03-18T17:19:45.050 回答