在另一个JNI函数中使用时,Oop会损坏 [英] Oop gets corrupted when using in another JNI function

查看:49
本文介绍了在另一个JNI函数中使用时,Oop会损坏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The question is can we cache jclass and jmethodID across different JNI methods invocation?

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

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.

这是一个简单的例子:

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的指针.这是本机方法的实现:

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;
}

问题是当尝试在Java_com_test_Main_doAnotherAction中创建对象时取消引用0时程序崩溃.在object_alloc函数调用java_lang_Class::as_Klass(oopDesc*)时发生崩溃.

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*).

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 here seems to contain a pointer to the relevant Oop. What I noticed is the first 5 times where no crash occurred:

rdi            0x7191eb228

坠毁的情况是

rdi            0x7191eb718

导致0x0被退回并崩溃.

在不同的JNI函数中使用jclassjmethodID时,什么会损坏Oop?如果我使用在本地找到的jclassjmethodID创建对象,则一切正常.

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:分析核心转储后,我发现rdi的加载方式为

UPD: After analyzing core dump I figured out that the rdi is being loaded as

mov    rdi,r13
#...
mov    rdi,QWORD PTR [rdi]

虽然r13在我的JNI函数中似乎没有更新...

While the r13 does not seem to be update inside JNI functions of mine...

推荐答案

跨JNI调用缓存jclass是一项主要工作(尽管很典型)错误.
jclass特殊情况jobject中的>-它是JNI参考,应进行管理.

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.

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

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.

为了在JNI调用之间缓存jclass,您可以使用

In order to cache jclass across JNI calls, you may convert it to a global reference using NewGlobalRef function.

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

jthread, jstring, jarray are other examples of jobjects, and they should also be managed.

JNIEnv*也一定不能被缓存,因为它仅在

JNIEnv* must not be cached, too, because it is valid only in the current thread.

同时jmethodIDjfieldID可以在JNI调用之间安全地重用-它们明确标识JVM中的方法/字段,并且

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.

这篇关于在另一个JNI函数中使用时,Oop会损坏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆