问题
The question is can we cache jclass
and jmethodID
across different JNI methods invocation?
I faced some strange behavior when trying to create an object of some specific class with cached jclass
and jmethodID
from another JNI method invocation.
Here is a simple example:
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;
}
}
What the JNI functions do is just creating objects of the class MyClass
. The function doSomeAction
return a pointer pointing to a cached jclass
and jmethodID
. Here is the implementation of native methods:
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;
}
The problem is the program is crash when dereferencing 0
when trying to create object in the Java_com_test_Main_doAnotherAction
. Crash occurs at object_alloc
function calling java_lang_Class::as_Klass(oopDesc*)
.
Dissasmebly of java_lang_Class::as_Klass(oopDesc*)
is
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
here seems to contain a pointer to the relevant Oop
. What I noticed is the first 5 times where no crash occurred:
rdi 0x7191eb228
The crash case is
rdi 0x7191eb718
Causing 0x0
to be returned and crash.
What does get Oop
corrupted when using jclass
and jmethodID
across different JNI
functions? If I create objects with a locally found jclass
and jmethodID
everything works just fine.
UPD: After analyzing core dump I figured out that the rdi is being loaded as
mov rdi,r13
#...
mov rdi,QWORD PTR [rdi]
While the r13
does not seem to be update inside JNI functions of mine...
回答1:
Caching jclass
across JNI calls is a major (though typical) mistake.jclass
is a special case of jobject
- it is a JNI reference and should be managed.
As JNI spec says, all Java objects returned by JNI functions are local references.
So, FindClass
returns a local JNI reference which becomes invalid as soon as the native method returns. That is, GC will not update the reference if the object is moved, or another JNI call may reuse the same slot for a different JNI reference.
In order to cache jclass
across JNI calls, you may convert it to a global reference using NewGlobalRef function.
jthread
, jstring
, jarray
are other examples of jobjects
, and they should also be managed.
JNIEnv*
must not be cached, too, because it is valid only in the current thread.
At the same time jmethodID
and jfieldID
can be safely reused across JNI calls - they unambiguously identify a method/field in the JVM and are intended for repeated use as long as the holder class is alive. However, they may also become invalid if the holder class happens to be garbage collected.
来源:https://stackoverflow.com/questions/56246852/oop-gets-corrupted-when-using-in-another-jni-function