Java 使用调用API的JNI内存管理

Java 使用调用API的JNI内存管理,java,c,memory-management,java-native-interface,Java,C,Memory Management,Java Native Interface,当我使用JNI方法构建java对象时,为了将其作为参数传递给使用JNI调用API调用的java方法,我如何管理其内存 以下是我的工作内容: 我有一个C对象,它有一个比free()更复杂的析构函数方法。这个C对象将与Java对象相关联,一旦应用程序使用Java对象完成,我就不再需要C对象了 我像这样创建Java对象(为了清晰起见,省略了错误检查): c_object=c_object_create(); class=(*env)->FindClass(env,“my.class.name”); 构

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

以下是我的工作内容:

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

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

c_object=c_object_create();
class=(*env)->FindClass(env,“my.class.name”);
构造函数=(*env)->GetMethodID(env,类,”,“(J)V”);
实例=(*env)->NewObject(env,类,构造函数,(jlong)c_对象);
method=(*env)->GetMethodID(env,other_类,“doSomeWork”,“Lmy.class.name)V);
(*env)->CallVoidMethod(env,other_类,方法,实例);
那么,既然我已经处理完
实例
,我该如何处理它呢?理想情况下,我希望让VM负责垃圾收集;使用
实例
完成后,如果在我提供给它的指针上也调用
c\u object\u destroy()
,那就太棒了。这可能吗


一个单独但相关的问题与我在这样的方法中创建的Java实体的范围有关;我是否必须手动释放,比如上面的
构造函数
、或
方法
?JNI文档在适当的内存管理问题上含糊不清,令人沮丧。

GC将收集您的实例,但它不会自动释放本机代码中分配的非java堆内存。在类中应该有显式方法来释放c_对象实例

在这种情况下,我建议使用终结器检查c_对象是否已释放,并释放它,如果未释放,则记录消息

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


建议避免直接使用JNI,而使用or(两者都生成代码并可以静态链接)。

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

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

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

  • 据推测,PhantomReference也可以用来解决这个问题,但我不确定这样的解决方案到底是如何工作的


  • 实际上,我不同意你关于JNI文档的看法。我发现大多数重要问题的答案都非常清晰,即使关于管理本地和全球参考文献的章节本可以更详细地阐述。

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

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

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

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

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

    全局引用是使用NewGlobalRef()创建的(从本地引用)。它们被添加到一个特殊的注册表中,必须手动解除分配。全球的
    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);