性能问答>为什么 JNI 的 FindClass 方法有奇怪的副作用?>
1回复

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



我在用 JNI 的时候遇到了一个很奇怪的问题,有人能帮忙分析一下到底是怎么回事吗

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

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

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

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

注释掉 (*) 行(应该是空操作!)后,用 Class.equals 方法发现 Integer.class 的一个实例与 Integer.class 的另一个实例不相等。 取消注释该行,java.lang.Integer 在 test1 方法中被查找了两次而不是一次,并且由于某种原因,现在发现 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));
}
677 阅读
请先登录,再评论

Integer_class_0 是一个本地引用,当 Java_pkg_Test_test0 返回时它会失效。 你在 Java_pkg_Test_test1 中使用它是没有意义的。 除非你先使用 NewGlobalRef 将其设为全局引用.
JNI 返回对象引用的句柄,所以直接比较返回的句柄是没有意义的。 如果你想查看两个句柄是否指向同一个对象,可以用 env->IsSameObject(handle1, handle2) 或 equals(虽然检查的不只是对象身份)。
我能想到的最好解释是“no-op”FindClass 调用在本地引用表的第一个槽(这也是 Integer_class_0 指向的位置)存储了一个本地引用,这不小心让 Integer_class_0 再次引用了 Integer 类。 验证这一假设的一种方法是:

env->FindClass("java/lang/String"); // replaces the local reference pointed to by Integer_class_0
jclass clsClass = env->FindClass("java/lang/Class");
jmethodID midClassToString = env->GetMethodID("toString", "()Ljava/lang/String;");
jstring className = (jstring) env->CallObjectMethod(Integer_class_0, midClassToString);

生成的 className 对象应该包含类 java.lang.String 而不是类 java.lang.Integer

7月前