oop在另一个jni函数中使用时损坏

r8uurelv  于 2021-07-03  发布在  Java
关注(0)|答案(1)|浏览(338)

问题是我们能不能 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函数中似乎没有更新。。。
kcugc4gi

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类碰巧被垃圾收集,它们也可能变得无效。

相关问题