JNI 库在垃圾回收时释放内存?
JNI libraries deallocate memory upon garbage collection?
我正在使用 JCUDA,想知道 JNI 对象是否足够智能,可以在垃圾收集时解除分配?我能理解为什么这可能不适用于所有情况,但我知道它适用于我的情况,所以我的后续问题是:我怎样才能做到这一点?我可以设置 "mode" 吗?我需要构建一个抽象层吗?或者也许答案真的是 "no don't ever try that" 那么为什么不呢?
编辑:我指的只是通过 JNI 创建的本机对象,而不是 Java 对象。我知道所有 Java 个对象都被平等对待 W.R.T。垃圾回收。
Java 在 JNI 中创建的对象等同于所有其他 Java 对象,并且在它们的时间到来时被垃圾收集和销毁。为了不让这样的对象过早销毁,我们经常使用JNI函数env->NewGlobalRef()
(但它的使用绝不仅限于native创建的对象)
另一方面,本机对象不受垃圾回收的影响。
这里有两种情况。
- 本机代码分配 Java 个对象。这些对象与所有其他 Java 对象一样是 GC。如果本机搞砸并持有强引用,它可以防止 GC。
- 本机代码分配本机内存。 GC 对此一无所知;由图书馆安排释放它。一种方法是让一个 Java 对象带有一个终结器,它可以进行必要的 JNI 调用以释放本机内存。
通常,此类库不会因垃圾收集而释放内存。特别是:JCuda 不会这样做,并且没有选项或 "mode" 可以做到这一点。
原因很简单:不行。
你经常会有这样的模式:
void doSomethingWithJCuda()
{
CUdeviceptr data = new CUdeviceptr();
cuMemAlloc(data, 1000);
workWith(data);
// *(See notes below)
}
这里分配了本机内存,Java对象作为本机内存的"handle"。
在最后一行,data
对象超出范围。因此,它有资格进行垃圾收集。但是,有两个问题:
1.垃圾收集器将仅销毁Java对象,不会 释放使用 cuMemAlloc
或任何其他本机调用分配的内存。
因此您通常必须通过显式调用
来释放本机内存
cuMemFree(data);
离开方法之前。
2. 您不知道 Java 对象何时会被垃圾回收 - 或者它是否会被垃圾回收。
一个常见的误解是,当一个对象不再可访问时,它就会被垃圾收集,但这不一定是真的。
正如 在他的回答中指出的那样:
One means is to have a Java object with a finalizer that makes the necessary JNI call to free native memory.
简单地覆盖这些 "handle" 对象的 finalize()
方法并在那里执行 cuMemFree(this)
调用似乎是一个可行的选择。例如,JavaCL(一个库也允许将 GPU 与 Java 一起使用,因此在概念上与 JCuda 有点相似)的作者已经对此进行了尝试。
但它根本不起作用:即使 Java 对象不再可达,这 not 意味着它将立即被垃圾收集。
你根本不知道什么时候调用finalize()
方法。
这很容易导致严重的错误:当你有 100 MB 的 GPU 内存时,你可以使用 10 个 CUdeviceptr
对象,每个对象分配 10MB。您的 GPU 内存已满。但是对于Java来说,这几个CUdeviceptr
对象只占几个字节,在应用程序运行期间可能根本不会调用finalize()
方法,因为JVM根本不需要回收这几个字节的内存。 (这里省略了关于 hacky 解决方法的讨论,比如调用 System.gc()
左右——底线是:它不起作用)。
所以回答你的实际问题:JCuda 是一个非常低级的库。这意味着您拥有全部权力,但也有手动内存管理的全部责任。我知道这是"inconvenient"。当我开始创建 JCuda 时,我最初打算将它作为面向对象的包装器库的低级后端。但是,为像 CUDA 这样的复杂通用库创建一个 健壮、稳定且普遍适用的 抽象层具有挑战性,我不敢处理这样的项目 - 最后但并非最不重要的原因隐含的复杂性……比如垃圾收集……
我正在使用 JCUDA,想知道 JNI 对象是否足够智能,可以在垃圾收集时解除分配?我能理解为什么这可能不适用于所有情况,但我知道它适用于我的情况,所以我的后续问题是:我怎样才能做到这一点?我可以设置 "mode" 吗?我需要构建一个抽象层吗?或者也许答案真的是 "no don't ever try that" 那么为什么不呢?
编辑:我指的只是通过 JNI 创建的本机对象,而不是 Java 对象。我知道所有 Java 个对象都被平等对待 W.R.T。垃圾回收。
Java 在 JNI 中创建的对象等同于所有其他 Java 对象,并且在它们的时间到来时被垃圾收集和销毁。为了不让这样的对象过早销毁,我们经常使用JNI函数env->NewGlobalRef()
(但它的使用绝不仅限于native创建的对象)
另一方面,本机对象不受垃圾回收的影响。
这里有两种情况。
- 本机代码分配 Java 个对象。这些对象与所有其他 Java 对象一样是 GC。如果本机搞砸并持有强引用,它可以防止 GC。
- 本机代码分配本机内存。 GC 对此一无所知;由图书馆安排释放它。一种方法是让一个 Java 对象带有一个终结器,它可以进行必要的 JNI 调用以释放本机内存。
通常,此类库不会因垃圾收集而释放内存。特别是:JCuda 不会这样做,并且没有选项或 "mode" 可以做到这一点。
原因很简单:不行。
你经常会有这样的模式:
void doSomethingWithJCuda()
{
CUdeviceptr data = new CUdeviceptr();
cuMemAlloc(data, 1000);
workWith(data);
// *(See notes below)
}
这里分配了本机内存,Java对象作为本机内存的"handle"。
在最后一行,data
对象超出范围。因此,它有资格进行垃圾收集。但是,有两个问题:
1.垃圾收集器将仅销毁Java对象,不会 释放使用 cuMemAlloc
或任何其他本机调用分配的内存。
因此您通常必须通过显式调用
来释放本机内存cuMemFree(data);
离开方法之前。
2. 您不知道 Java 对象何时会被垃圾回收 - 或者它是否会被垃圾回收。
一个常见的误解是,当一个对象不再可访问时,它就会被垃圾收集,但这不一定是真的。
正如
One means is to have a Java object with a finalizer that makes the necessary JNI call to free native memory.
简单地覆盖这些 "handle" 对象的 finalize()
方法并在那里执行 cuMemFree(this)
调用似乎是一个可行的选择。例如,JavaCL(一个库也允许将 GPU 与 Java 一起使用,因此在概念上与 JCuda 有点相似)的作者已经对此进行了尝试。
但它根本不起作用:即使 Java 对象不再可达,这 not 意味着它将立即被垃圾收集。
你根本不知道什么时候调用finalize()
方法。
这很容易导致严重的错误:当你有 100 MB 的 GPU 内存时,你可以使用 10 个 CUdeviceptr
对象,每个对象分配 10MB。您的 GPU 内存已满。但是对于Java来说,这几个CUdeviceptr
对象只占几个字节,在应用程序运行期间可能根本不会调用finalize()
方法,因为JVM根本不需要回收这几个字节的内存。 (这里省略了关于 hacky 解决方法的讨论,比如调用 System.gc()
左右——底线是:它不起作用)。
所以回答你的实际问题:JCuda 是一个非常低级的库。这意味着您拥有全部权力,但也有手动内存管理的全部责任。我知道这是"inconvenient"。当我开始创建 JCuda 时,我最初打算将它作为面向对象的包装器库的低级后端。但是,为像 CUDA 这样的复杂通用库创建一个 健壮、稳定且普遍适用的 抽象层具有挑战性,我不敢处理这样的项目 - 最后但并非最不重要的原因隐含的复杂性……比如垃圾收集……