在另一个JNI函数中使用时,Oop会损坏

时间:2019-05-21 21:57:02

标签: java jvm java-native-interface jvm-hotspot

问题是我们可以跨不同的JNI方法调用缓存jclassjmethodID吗?

当尝试通过另一个JNI方法调用使用缓存的jclassjmethodID创建某个特定类的对象时,我遇到了一些奇怪的行为。

这是一个简单的例子:

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返回一个指向缓存的jclassjmethodID的指针。这是本机方法的实现:

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的{​​{1}}函数发生崩溃。

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的指针。我注意到的是前5次没有发生崩溃:

Oop

崩溃案例是

rdi            0x7191eb228

导致rdi 0x7191eb718 被退回并崩溃。

在不同的0x0函数中使用Oopjclass时,jmethodID损坏了什么?如果我使用在本地找到的JNIjclass创建对象,一切正常。

UPD:分析核心转储后,我发现rdi的加载方式为

jmethodID

虽然mov rdi,r13 #... mov rdi,QWORD PTR [rdi] 在我的JNI函数中似乎没有更新...

1 个答案:

答案 0 :(得分:5)

在JNI调用中缓存jclass是一个重大(though typical)错误。
jclassjobject的{​​{3}}-它是JNI引用,应加以管理。

按照JNI规范special case JNI函数返回的所有Java对象都是本地引用。 因此,FindClass返回一个本地JNI引用,该本地JNI引用在本机方法返回后立即变得无效。也就是说,如果移动了对象,GC将不会更新引用,否则另一个JNI调用可能会将同一插槽重新用于其他JNI引用。

为了在JNI调用之间缓存jclass,您可以使用says函数将其转换为全局引用。

jthreadjstringjarrayjobjects的其他示例,也应对其进行管理。

JNIEnv*也不得缓存,因为它仅NewGlobalRef有效。

同时jmethodIDjfieldID可以在JNI调用之间安全地重用-它们明确标识JVM中的方法/字段,并且只要持有者类为in the current thread活。但是,如果holder类恰好被垃圾回收,它们也可能变得无效。