6

当我使用 JNI 方法构建 java 对象时,为了将它作为参数传递给我使用 JNI 调用 API 调用的 java 方法,我如何管理它的内存?

这是我正在使用的内容:

我有一个 C 对象,它的析构函数方法比free(). 这个 C 对象将与一个 Java 对象相关联,一旦应用程序完成了 Java 对象,我就不再需要 C 对象了。

我正在像这样创建 Java 对象(为清楚起见,省略了错误检查):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

那么,既然我已经完成了instance,我该怎么办呢?理想情况下,我想把垃圾收集留给虚拟机;当它完成时,instance如果它还调用c_object_destroy()我提供给它的指针,那就太棒了。这可能吗?

一个单独但相关的问题与我在这样的方法中创建的 Java 实体的范围有关;我是否必须手动释放,比如说,,,classconstructor以上method?JNI 文档在正确内存管理的主题上令人沮丧地模糊(根据我的判断)。

4

4 回答 4

10

JNI 规范涵盖了谁“拥有”在 JNI 方法中创建的 Java 对象的问题。您需要区分本地引用和全局引用。

当 JVM 对本机代码进行 JNI 调用时,它会设置一个注册表来跟踪调用期间创建的所有对象。在本机调用期间创建的任何对象(即从 JNI 接口函数返回)都将添加到此注册表中。对此类对象的引用称为本地引用。当本机方法返回 JVM 时,本机方法调用期间创建的所有本地引用都将被销毁。如果您在本机方法调用期间向 JVM 进行回调,则当控制权返回本机方法时,本地引用仍然有效。如果从本机代码调用的 JVM 再次调用本机代码,则会创建一个新的本地引用注册表,并应用相同的规则。

(实际上,您可以使用 JNI 接口实现您自己的 JVM 可执行文件(即 java.exe),方法是创建一个 JVM(从而接收一个 JNIEnv * 指针),在命令行上查找给定的类,然后调用main() 方法。)

从 JNI 接口方法返回的所有引用都是本地的。这意味着在正常情况下,您不需要手动释放 JNI 方法返回的引用,因为它们在返回 JVM 时会被销毁。有时您仍然想“过早地”销毁它们,例如当您想要在返回 JVM 之前删除大量本地引用时。

使用 NewGlobalRef() 创建全局引用(从本地引用)。它们被添加到一个特殊的注册表中,并且必须手动解除分配。全局引用仅用于本机代码需要跨多个 JNI 调用保存引用的 Java 对象,例如,如果您有应传播回 Java 的本机代码触发事件。在这种情况下,JNI 代码需要存储对要接收事件的 Java 对象的引用。

希望这能稍微澄清一下内存管理问题。

于 2008-10-18T22:11:51.050 回答
5

有几种策略可以回收本地资源(对象、文件描述符等)

  1. 在 finalize() 期间调用 JNI 方法来释放资源。有些人建议不要实现 finalize,基本上你不能确定你的本地资源是否被释放。对于内存等资源,这可能不是问题,但如果您有一个文件需要在可预测的时间刷新,finalize() 可能不是一个好主意。

  2. 手动调用清理方法。如果您在某个时间点知道必须清理资源,这将很有用。当我有一个必须在卸载 JNI 代码中的 DLL 之前释放的资源时,我使用了这种方法。为了允许稍后重新加载 DLL,在尝试卸载 DLL 之前,我必须确保对象确实被释放。仅使用 finalize(),我不会得到这个保证。这可以与 (1) 结合使用,以允许在 finalize() 期间或在手动调用的清理方法中分配资源。(您可能需要 WeakReferences 的规范映射来跟踪哪些对象需要调用其清理方法。)

  3. 据说PhantomReference也可以用来解决这个问题,但我不确定这样的解决方案究竟是如何工作的。

实际上,我不得不在 JNI 文档上不同意你的观点。我发现JNI 规范在大多数重要问题上都非常清楚,即使关于管理本地和全局引用的部分可以更详细地阐述。

于 2008-10-18T16:37:32.333 回答
0

GC 会收集您的实例,但不会自动释放在本机代码中分配的非 Java 堆内存。你应该在你的类中有明确的方法来释放 c_object 实例。

这是我建议使用终结器检查 c_object 是否已释放并释放它的情况之一,如果没有则记录一条消息。

一个有用的技术是在 Java 类构造函数中创建一个 Throwable 实例并将其存储在一个字段中(或者只是内联初始化该字段)。如果终结器检测到类没有被正确处理,它将打印堆栈跟踪,精确定位分配堆栈。

一个建议是避免直接使用 JNI 并使用gluegenSwig(两者都生成代码并且可以静态链接)。

于 2008-10-18T07:44:58.960 回答
0

回复:“一个单独但相关的问题”......当您在“本地”上下文中使用它们时,您不需要手动释放 jclass、jfieldID 和 jmethodID。您获得的任何实际对象引用(不是 jclass、jfieldID、jmethodID)都应该使用 DeleteLocalRef 释放。

于 2008-10-18T20:10:49.453 回答