问题是我们能不能 jclass
以及 jmethodID
跨不同的jni方法调用?
当我试图用cached创建某个特定类的对象时,遇到了一些奇怪的行为 jclass
以及 jmethodID
从另一个jni方法调用。
下面是一个简单的例子:
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;
}
问题是程序在解引用时崩溃 0
尝试在中创建对象时 Java_com_test_Main_doAnotherAction
. 崩溃发生在 object_alloc
函数调用 java_lang_Class::as_Klass(oopDesc*)
.
不愉快 java_lang_Class::as_Klass(oopDesc*)
是
Dump of assembler code for function _ZN15java_lang_Class8as_KlassEP7oopDesc:
0x00007f7f6b02eeb0 <+0>: movsxd rax,DWORD PTR [rip+0x932ab5] # 0x7f7f6b96196c <_ZN15java_lang_Class13_klass_offsetE>
0x00007f7f6b02eeb7 <+7>: push rbp
0x00007f7f6b02eeb8 <+8>: mov rbp,rsp
0x00007f7f6b02eebb <+11>: pop rbp
0x00007f7f6b02eebc <+12>: mov rax,QWORD PTR [rdi+rax*1]
0x00007f7f6b02eec0 <+16>: ret
``` `rdi` 这里似乎包含一个指向相关 `Oop` . 我注意到前5次没有发生车祸:
rdi 0x7191eb228
坠机事件是
rdi 0x7191eb718
造成 `0x0` 被送回并坠毁。
你得到了什么 `Oop` 使用时损坏 `jclass` 以及 `jmethodID` 跨越不同的 `JNI` 功能?如果我用本地找到的 `jclass` 以及 `jmethodID` 一切正常。
upd:在分析了内核转储之后,我发现rdi被加载为
mov rdi,r13
...
mov rdi,QWORD PTR [rdi]
而 `r13` 在我的jni函数中似乎没有更新。。。
1条答案
按热度按时间kcugc4gi1#
缓存
jclass
跨jni调用是一个重大(尽管是典型的)错误。jclass
是一个特殊的例子jobject
-它是一个jni引用,应该进行管理。正如jni规范所说,jni函数返回的所有java对象都是本地引用。所以,
FindClass
返回本地jni引用,该引用在本机方法返回时立即变为无效。也就是说,如果对象被移动,gc将不会更新引用,或者另一个jni调用可能会将相同的槽重新用于不同的jni引用。为了缓存
jclass
在jni调用中,可以使用NewGlobalRef
功能。jthread
,jstring
,jarray
还有其他的例子吗jobjects
,他们也应该得到管理。JNIEnv*
也不能缓存,因为它仅在当前线程中有效。同时
jmethodID
以及jfieldID
可以安全地跨jni调用重用—它们明确地标识jvm中的方法/字段,并且只要holder类还活着,就可以重复使用。但是,如果holder类碰巧被垃圾收集,它们也可能变得无效。