为什么 JNI 的 FindClass 方法有奇怪的副作用?

Luk*_*son 2 java java-native-interface jvm

我在使用 JNI 时遇到了一个非常奇怪的问题。有人可以帮我理解这里有什么问题吗?

如果我按原样运行下面的代码,我会看到:

(a) 7fb6f022faf0 7fb6f022fb00 0
(b) 7fb6f022faf8 7fb6f022fb00 1
Run Code Online (Sandbox Code Playgroud)

如果我取消注释标记为 的行// (*),那么我会得到:

(a) 7f6ce822faf0 7f6ce822fb08 1
(b) 7f6ce822fb00 7f6ce822fb08 1
Run Code Online (Sandbox Code Playgroud)

(*)注释掉该行(这应该是无操作!Integer.classInteger.class,使用Class.equals方法发现 的一个实例与 的另一个实例不相等。取消注释该行,java.lang.Integertest1方法中查找了两次而不是一次,由于某种原因,现在Integer.class发现两个实例是相等的!(这是在 JDK 16 上。)

有没有搞错?我完全不明白这...

pkg/Test.java

package pkg;
public class Test {
    public static native void test0();
    public static native void test1(Object... args);

    public static void main(String[] args) throws Exception {
        test0();
        test1(7);
    }
}
Run Code Online (Sandbox Code Playgroud)

test.c

#include <jni.h>
#include <stdio.h>

jclass Integer_class_0;

JNIEXPORT void JNICALL Java_pkg_Test_test0(JNIEnv *env, jclass ignored) {
    Integer_class_0 = (*env)->FindClass(env, "java/lang/Integer");
}

JNIEXPORT void JNICALL Java_pkg_Test_test1(JNIEnv *env, jclass ignored, 
            jobjectArray args) {
    //(*env)->FindClass(env, "java/lang/Integer");    // (*)
    jobject arg = (*env)->GetObjectArrayElement(env, args, 0);
    jclass arg_type = (*env)->GetObjectClass(env, arg);
    jclass Integer_class_1 = (*env)->FindClass(env, "java/lang/Integer");

    jclass cls_class = (*env)->FindClass(env, "java/lang/Class");
    jmethodID cls_equals_methodID =
            (*env)->GetMethodID(env, cls_class, "equals", "(Ljava/lang/Object;)Z");

    printf("(a) %lx %lx %d\n", Integer_class_0, Integer_class_1,
            (*env)->CallBooleanMethod(env,
                    Integer_class_0, cls_equals_methodID, Integer_class_1));
    printf("(b) %lx %lx %d\n", arg_type, Integer_class_1,
            (*env)->CallBooleanMethod(env,
                    arg_type, cls_equals_methodID, Integer_class_1));
}

Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 6

注意JNI 规范

JNI 将本机代码使用的对象引用分为两类:局部引用和全局引用。本地引用在本地方法调用期间有效,并在本地方法返回后自动释放。全局引用在被显式释放之前一直有效。

对象作为本地引用传递给本地方法。JNI 函数返回的所有 Java 对象都是本地引用。JNI 允许程序员从本地引用创建全局引用。期望 Java 对象的 JNI 函数接受全局和局部引用。本机方法可能会返回对 VM 的本地或全局引用作为其结果。

您从方法调用中获取本地引用并尝试(*env)->FindClass(env, "java/lang/Integer")test0方法调用中使用它test1,尽管它在test0返回时已自动释放。

访问释放的内存或无效的引用可能会产生任意影响,包括明显不相关的操作改变结果的可能性。

要获得在方法调用之间持续存在的引用,您必须使用NewGlobalRef.

顺便说一句,您可以使用IsSameObject来比较引用,而无需调用equals(重写时会有不同的语义)。