Java JNI确保本地能力——为什么?

Java JNI确保本地能力——为什么?,java,java-native-interface,Java,Java Native Interface,请在此处查看JNI文档: 具体而言,请查看EnsureLocalCapacity功能说明中所述内容: 为了向后兼容,VM分配本地引用 确保的能力。(作为调试支持,VM可能会提供 用户警告正在创建太多本地引用。在 在JDK中,程序员可以提供-verbose:jni命令行选项 打开这些消息。)如果没有更多本地消息,VM将调用FatalError 可以创建超出保证容量的引用 此外,看看PushLocalFrame如何接受“容量”参数。(顺便说一句,它没有提到这是硬限制,还是像EnsureLocalCa

请在此处查看JNI文档:

具体而言,请查看EnsureLocalCapacity功能说明中所述内容:

为了向后兼容,VM分配本地引用 确保的能力。(作为调试支持,VM可能会提供 用户警告正在创建太多本地引用。在 在JDK中,程序员可以提供-verbose:jni命令行选项 打开这些消息。)如果没有更多本地消息,VM将调用FatalError 可以创建超出保证容量的引用

此外,看看PushLocalFrame如何接受“容量”参数。(顺便说一句,它没有提到这是硬限制,还是像EnsureLocalCapacity那样的软限制)

这些关于本地参考容量的废话到底从何而来?文档中说,虚拟机将愿意分配超出当前正式容量的引用,那么为什么它不这样做,并将所有这些容量杂乱无章地保留在API之外呢

以C为例,我感觉好像被要求预先计划我将要进行多少次malloc()调用,这感觉有点可笑


有什么重要的事情我只是没有看到吗?

这是我的猜测。我怀疑不断增长的本地引用容量是昂贵的,该函数允许每次本机方法调用请求所需的容量一次。我认为这更多的是关于性能而不是正确性。

据我所知,本地引用表大小是预定义的,如果分配的数量超过这个数量,它将崩溃。所以并不是说VM会永远给你更多的本地参考。在Android上使用JNI时,我曾多次遇到过这种情况。通常很难找到这样的内存泄漏。每次对JNI本地引用执行操作时,调用env->DeleteLocalRef()只会使代码变得混乱。因此,我们不必用DeleteLocalRef()调用来混乱代码,只需确保不创建太多引用,并保持代码的整洁即可。即使我们计划分配更多的本地引用,我们仍然可以只调用PushLocalFrame()/PopLocalFrame(),避免多次调用DeleteLocalRef()


因此,为了给您一个“C”的类比,我们要求您预先计划对malloc的调用,以避免在最后对free()进行所有必要的调用。

JNI文档在这个主题上是模糊的。我在实践中发现,有时局部引用的数量似乎是无限的,但实际上可能会耗尽局部引用,例如在使用JNI调用对数组进行迭代时。什么是本地参考?它是一个jobject(或jarray、jstring等),您可以从以下任何来源获得:

  • 从NewObjectV()或GetObjectArrayElement()等原始JNI调用返回
  • 从CallObjectMethodV()之类的方法调用返回
  • 作为参数传递给本机函数
  • 可能是我忘了的其他人
其中,您通常不必担心在方法参数中传递给您的引用,除非您打算在当前方法调用之外保留引用,在这种情况下,您应该将其转换为全局引用

值得注意的是,您不能挂起当前方法范围之外的本地引用。如果要将其存储在C++对象中,则必须将其转换为全局对象。本地参考有两个优点

  • 当当前帧超出范围时,它们总是自动释放
  • 它们比转换为全球服务更快
  • 像大多数JNI错误一样,任何对引用的误用都很难诊断,所以您真的不想得到一个。你最好的防御方法是始终如一的方法。根据我的经验,将本地转换为全球的成本相当小。除非您有非常严格的性能需求,否则我建议您执行以下操作。
    有鉴于此,我的规则是——始终使用以下代码将局部参照转换为全局参照:

    jobject localjo = ...
    jobject globaljo = env->NewGlobalRef(localjo);
    env->DeleteLocalRef(localjo);
    
    此时,您可以知道每个ref都是全局的,完成后,您需要记住对每个ref执行env->DeleteGlobalRef(globaljo)。在C++中,可以在DRTR中调用一个包装类来调用JNI引用,其中调用Env-> DeleTeGoBalbReFor()。但是,由于JNI不会为您计算JNI引用,因此您可能还需要在中构建它