Java 在另一个JNI函数中使用时,Oop会损坏
问题是我们能否跨不同的JNI方法调用缓存jclass和JMethodi 当我试图通过另一个JNI方法调用使用缓存的jclass和JMethodi创建某个特定类的对象时,我遇到了一些奇怪的行为 下面是一个简单的例子:Java 在另一个JNI函数中使用时,Oop会损坏,java,jvm,java-native-interface,jvm-hotspot,Java,Jvm,Java Native Interface,Jvm Hotspot,问题是我们能否跨不同的JNI方法调用缓存jclass和JMethodi 当我试图通过另一个JNI方法调用使用缓存的jclass和JMethodi创建某个特定类的对象时,我遇到了一些奇怪的行为 下面是一个简单的例子: public class Main { static { System.loadLibrary("test-crash"); } public static void main(String args[]) throws Interrupte
public class Main {
static {
System.loadLibrary("test-crash");
}
public static void main(String args[]) throws InterruptedException {
Thread.sleep(20000);
doAnotherAction(doSomeAction());
}
private static native long doSomeAction();
private static native void doAnotherAction(long ptr);
}
public class MyClass {
public int a;
public MyClass(int a) {
if(a == 10){
throw new IllegalArgumentException("a == 10");
}
this.a = a;
}
}
JNI函数所做的只是创建类MyClass的对象。函数doSomeAction返回一个指向缓存的jclass和jmethodID的指针。以下是本机方法的实现:
struct test{
jclass mc;
jmethodID ctor;
};
JNIEXPORT jlong JNICALL Java_com_test_Main_doSomeAction
(JNIEnv *env, jclass jc){
(void) jc;
jclass mc = (*env)->FindClass(env, "com/test/MyClass");
jmethodID ctor = (*env)->GetMethodID(env, mc, "<init>", "(I)V");
struct test *test_ptr = malloc(sizeof *test_ptr);
test_ptr->mc = mc;
test_ptr->ctor = ctor;
printf("Creating element0\n");
jobject ae1 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
(void) ae1;
printf("Creating element0\n");
jobject ae2 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
(void) ae2;
printf("Creating element0\n");
jobject ae3 = (*env)->NewObject(env, test_ptr->mc, test_ptr->ctor, (jint) 0);
(void) ae3;
return (intptr_t) test_ptr;
}
JNIEXPORT void JNICALL Java_com_test_Main_doAnotherAction
(JNIEnv *env, jclass jc, jlong ptr){
(void) jc;
struct test *test_ptr= (struct test *) ptr;
jclass mc = test_ptr->mc;
jmethodID ctor = test_ptr->ctor;
printf("Creating element\n");
jobject ae1 = (*env)->NewObject(env, mc, ctor, (jint) 0);
(void) ae1;
printf("Creating element\n");
jobject ae2 = (*env)->NewObject(env, mc, ctor, (jint) 0);
(void) ae2;
printf("Creating element\n");
jobject ae3 = (*env)->NewObject(env, mc, ctor, (jint) 0); //CRASH!!
(void) ae3;
}
rdi在这里似乎包含一个指向相关Oop的指针。我注意到的是前5次没有发生碰撞:
rdi 0x7191eb228
坠机事件是
rdi 0x7191eb718
导致返回0x0并崩溃
当跨不同的JNI函数使用jclass和JMethod时,Oop会损坏什么?如果我使用本地找到的jclass和JMethod创建对象,那么一切都很好
UPD:在分析了内核转储之后,我发现rdi被加载为
mov rdi,r13
#...
mov rdi,QWORD PTR [rdi]
虽然r13似乎没有在我的JNI函数中更新…跨JNI调用缓存jclass是一个重大错误。
jclass是jobject的一个组件-它是一个JNI引用,应该进行管理
作为JNI规范,JNI函数返回的所有Java对象都是本地引用。
因此,FindClass返回一个本地JNI引用,该引用在本机方法返回时立即失效。也就是说,如果对象被移动,GC将不会更新引用,或者另一个JNI调用可能会将相同的插槽重新用于不同的JNI引用
为了跨JNI调用缓存jclass,可以使用函数将其转换为全局引用
jthread、jstring、jarray是作业对象的其他示例,它们也应该被管理
JNIEnv*也不能被缓存,因为它仅有效
同时,jmethodID和jfieldID可以跨JNI调用安全地重用——它们明确地标识JVM中的方法/字段,并且只要holder类还活着就可以使用。但是,如果holder类恰好被垃圾收集,它们也可能变得无效。跨JNI调用缓存jclass是一个重大错误。
jclass是jobject的一个组件-它是一个JNI引用,应该进行管理
作为JNI规范,JNI函数返回的所有Java对象都是本地引用。
因此,FindClass返回一个本地JNI引用,该引用在本机方法返回时立即失效。也就是说,如果对象被移动,GC将不会更新引用,或者另一个JNI调用可能会将相同的插槽重新用于不同的JNI引用
为了跨JNI调用缓存jclass,可以使用函数将其转换为全局引用
jthread、jstring、jarray是作业对象的其他示例,它们也应该被管理
JNIEnv*也不能被缓存,因为它仅有效
同时,jmethodID和jfieldID可以跨JNI调用安全地重用——它们明确地标识JVM中的方法/字段,并且只要holder类还活着就可以使用。但是,如果holder类恰好被垃圾收集,它们也可能会变得无效。谢谢。问题是在哪里可以找到本地或全局引用的已创建对象的文档。我重新阅读了这些文件,但并不是很清楚。除了jmethodID和jfieldID之外,还有其他对象可以在JNI调用中安全地重用吗?@St.Antario我已经用JNI规范的相关链接更新了答案。@apangin我听说保存太多也有问题,你可以很容易地在本地和全局引用中找到太多。请您解释一下哪些对象是安全的,不占用空间,哪些应该小心缓存?jclass、jobject、jfieldID、jmethodID。如果缓存本地或全局引用,这些限制是否有差异?@MarcinK。这是一个不同的主题,因此请提出一个单独的问题,以避免在评论中进行长时间的讨论。另外,请澄清你的确切意思。@apangin这里是我提出的一个问题:谢谢。问题是在哪里可以找到本地或全局引用的已创建对象的文档。我重新阅读了这些文件,但并不是很清楚。除了jmethodID和jfieldID之外,还有其他对象可以在JNI调用中安全地重用吗?@St.Antario我已经用JNI规范的相关链接更新了答案。@apangin我听说保存太多也有问题,你可以很容易地在本地和全局引用中找到太多。请您解释一下哪些对象是安全的,不占用空间,哪些应该小心缓存?jclass、jobject、jfieldID、jmethodID。如果缓存本地或全局引用,这些限制是否有差异?@MarcinK。这是一个不同的主题,因此请提出一个单独的问题,以避免在评论中进行长时间的讨论。另外,请澄清你的确切意思。@apangin这里是我提出的一个问题:
mov rdi,r13
#...
mov rdi,QWORD PTR [rdi]