为什么 JNI 的 FindClass 方法有奇怪的副作用?
Why does JNI's `FindClass` method have weird side effects?
我遇到了一个非常奇怪的 JNI 问题。谁能帮我理解这里出了什么问题?
如果我按原样 运行 下面的代码,我会看到:
(a) 7fb6f022faf0 7fb6f022fb00 0
(b) 7fb6f022faf8 7fb6f022fb00 1
如果我取消注释标记为 // (*)
的行,那么我会得到:
(a) 7f6ce822faf0 7f6ce822fb08 1
(b) 7f6ce822fb00 7f6ce822fb08 1
注释掉 (*)
行( 这应该是一个空操作!),发现 Integer.class
的一个实例不等于Integer.class
的另一个实例,使用 Class.equals
方法。在取消注释行的情况下,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));
}
The JNI divides object references used by the native code into two categories: local and global references. Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed.
Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references. The JNI allows the programmer to create global references from local references. JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.
您在 test0
方法调用中从 (*env)->FindClass(env, "java/lang/Integer")
获取本地引用并尝试在 test1
方法调用中使用它,尽管它在 [=11] 时已被自动释放=] 返回。
访问释放的内存或无效的引用可能会产生任意影响,包括明显不相关的操作可能会改变结果。
要获得在方法调用之间持续存在的引用,您必须使用 NewGlobalRef
。
顺便说一下,您可以使用 IsSameObject
来比较引用,而无需调用 equals
(覆盖时会有不同的语义)。
整个实验都建立在不稳定的基础上:
Integer_class_0
为局部引用,在Java_pkg_Test_test0
returns时无效。试图在 Java_pkg_Test_test1
中使用它是没有意义的。除非你首先使用 NewGlobalRef
! 将其设为全局引用
- JNI returns 处理对象引用,所以直接比较返回的句柄没有意义。如果您想查看两个句柄是否指向同一个对象,请使用
env->IsSameObject(handle1, handle2)
或 equals
(尽管这不仅仅检查对象标识)。
我能想到的最好的解释是“无操作”FindClass 调用将本地引用存储在本地引用 table 的第一个槽位(这也是 Integer_class_0
点所在的位置) ,这不小心使 Integer_class_0
再次引用整数 class。验证此假设的一种方法是:
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
对象应该包含 class java.lang.String
而不是 class java.lang.Integer
。
我遇到了一个非常奇怪的 JNI 问题。谁能帮我理解这里出了什么问题?
如果我按原样 运行 下面的代码,我会看到:
(a) 7fb6f022faf0 7fb6f022fb00 0
(b) 7fb6f022faf8 7fb6f022fb00 1
如果我取消注释标记为 // (*)
的行,那么我会得到:
(a) 7f6ce822faf0 7f6ce822fb08 1
(b) 7f6ce822fb00 7f6ce822fb08 1
注释掉 (*)
行( 这应该是一个空操作!),发现 Integer.class
的一个实例不等于Integer.class
的另一个实例,使用 Class.equals
方法。在取消注释行的情况下,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));
}
The JNI divides object references used by the native code into two categories: local and global references. Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed.
Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references. The JNI allows the programmer to create global references from local references. JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.
您在 test0
方法调用中从 (*env)->FindClass(env, "java/lang/Integer")
获取本地引用并尝试在 test1
方法调用中使用它,尽管它在 [=11] 时已被自动释放=] 返回。
访问释放的内存或无效的引用可能会产生任意影响,包括明显不相关的操作可能会改变结果。
要获得在方法调用之间持续存在的引用,您必须使用 NewGlobalRef
。
顺便说一下,您可以使用 IsSameObject
来比较引用,而无需调用 equals
(覆盖时会有不同的语义)。
整个实验都建立在不稳定的基础上:
Integer_class_0
为局部引用,在Java_pkg_Test_test0
returns时无效。试图在Java_pkg_Test_test1
中使用它是没有意义的。除非你首先使用NewGlobalRef
! 将其设为全局引用
- JNI returns 处理对象引用,所以直接比较返回的句柄没有意义。如果您想查看两个句柄是否指向同一个对象,请使用
env->IsSameObject(handle1, handle2)
或equals
(尽管这不仅仅检查对象标识)。
我能想到的最好的解释是“无操作”FindClass 调用将本地引用存储在本地引用 table 的第一个槽位(这也是 Integer_class_0
点所在的位置) ,这不小心使 Integer_class_0
再次引用整数 class。验证此假设的一种方法是:
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
对象应该包含 class java.lang.String
而不是 class java.lang.Integer
。