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

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

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

(a) 7fb6f022faf0 7fb6f022fb00 0
(b) 7fb6f022faf8 7fb6f022fb00 1

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

(a) 7f6ce822faf0 7f6ce822fb08 1
(b) 7f6ce822fb00 7f6ce822fb08 1

(*)注释掉该行(这应该是无操作!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);
    }
}

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));
}

回答

注意JNI 规范:

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

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

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

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

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


以上是为什么 JNI 的 FindClass 方法有奇怪的副作用?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>