首页 > 编程语言 >JVM方法调用——native方法到java方法

JVM方法调用——native方法到java方法

时间:2022-09-21 23:14:30浏览次数:74  
标签:__ java call result JVM off movptr save 方法

native方法到java方法

最为经典的一个JNI调用Java方法就是调用Main函数,下面顺便会介绍java的启动过程。

java的main函数在src/java.base/share/native/launcher/main.c,这个函数会处理一些有参数的内容,然后进入到libjli的JLI_Launch函数中。这个函数最主要的任务就是对libjvm.so进行了加载,同时获取了一些符号的地址,将这些地址保存在了如下一个结构体中:

/*
 * Pointers to the needed JNI invocation API, initialized by LoadJavaVM.
 */
typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
typedef jint (JNICALL *GetDefaultJavaVMInitArgs_t)(void *args);
typedef jint (JNICALL *GetCreatedJavaVMs_t)(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);

typedef struct {
    CreateJavaVM_t CreateJavaVM; 						 // JNI_CreateJavaVM
    GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs; // JNI_GetDefaultJavaVMInitArgs
    GetCreatedJavaVMs_t GetCreatedJavaVMs;				 // JNI_GetCreatedJavaVMs
} InvocationFunctions;

虚拟机以动态链接库的形式存在,除了享受到一般的动态链接库的好处之外,还可以将jvm嵌入到其他程序里面。

正式进入主类的main方法其实还有非常多的任务,主要就是初始化一些数据、生成一些代码,以及启动一些工作线程,这些任务主要在Threads::create_vminit_globals中完成。之所以要分为两个线程来完成,推测可能是便于处理异常?我也不太清楚这么设计的原因。在加载完主类之后就会开始执行main方法。

首先使用JNI接口JNIEnv_::CallStaticVoidMethod,这里使用的是C++的JNI接口和C的在风格上有一些不同。这个函数会调用位于src/hotspot/share/prims/jni.cpp的函数jni_CallStaticVoidMethod,下面是其定义:

JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
  JNIWrapper("CallStaticVoidMethod");
  HOTSPOT_JNI_CALLSTATICVOIDMETHOD_ENTRY(env, cls, (uintptr_t) methodID);
  DT_VOID_RETURN_MARK(CallStaticVoidMethod);

  va_list args;
  va_start(args, methodID);
  JavaValue jvalue(T_VOID);
  JNI_ArgumentPusherVaArg ap(methodID, args);
  jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
  va_end(args);
JNI_END

其做的事件有两件,首先是将可变参数构造为一个JNI_ArgumentPusherVaArg对象,这个对象能够提供对参数的访问。另外就是进入到jni_invoke_static,正式发起调用,接下来会依次去到JavaCalls::callos::os_exception_wrapperJavaCalls::call_helper,call_helper里面必须做的就是获取了入口点和处理java方法返回值,其中真正发起调用的地方在:

// do call
{ JavaCallWrapper link(method, receiver, result, CHECK); // 无法理解,除了对JNIHandleBlock的处理之外。
 { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner
  // generate_call_stub
  StubRoutines::call_stub()(
      (address)&link,
      // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
      result_val_address,          // see NOTE above (compiler problem)
      result_type,
      method(),
      entry_point,
      args->parameters(),
      args->size_of_parameters(),
      CHECK
  );

  result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
  // Preserve oop return value across possible gc points
  if (oop_result_flag) {
      thread->set_vm_result((oop) result->get_jobject());
  }
 }
} // Exit JavaCallWrapper (can block - potential return oop must be preserved)

里面有个JavaCallWrapper,暂时没有完全明白到底做了什么,里面获取了receiver和result指针,我觉得可能是和GC有关,另外的就是设置了JNIHandleBlock这个和线程的资源管理有关。StubRoutines::call_stub会获取一个CallStub类型的例程。

下面是CallStub的类型声明:

// Calls to Java
typedef void (*CallStub)(
    address   link,
    intptr_t* result,
    BasicType result_type,
    Method* method,
    address   entry_point,
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
);

一共有8个参数,amd64 abi下前6个参数使用寄存器进行传递所以还有两个参数需要使用栈传递,分别是size_of_parameters__thread__,进入了call_stub例程后的栈结构如下:

stackframe-enter-call_stub.drawio

这个例程由StubGenerator::generate_call_stub生成,负责栈的切换和调用的实现,栈的变化逻辑非常清晰,下面的代码已经有了注释,进入了java方法的入口之后的处理逻辑已经说过了:

// Call stub stack layout word offsets from rbp
enum call_stub_layout {
    rsp_after_call_off = -12,
    mxcsr_off          = rsp_after_call_off, // simd的寄存器。
    r15_off            = -11,
    r14_off            = -10,
    r13_off            = -9,
    r12_off            = -8,
    rbx_off            = -7,
    call_wrapper_off   = -6,
    result_off         = -5,
    result_type_off    = -4,
    method_off         = -3,
    entry_point_off    = -2,
    parameters_off     = -1,
    rbp_off            =  0,
    retaddr_off        =  1,
    parameter_size_off =  2,
    thread_off         =  3
};


address generate_call_stub(address& return_address) {
    assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 &&
           (int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off,
           "adjust this code");
    StubCodeMark mark(this, "StubRoutines", "call_stub");
    address start = __ pc();

    // same as in generate_catch_exception()!
    const Address rsp_after_call(rbp, rsp_after_call_off * wordSize);

    const Address call_wrapper  (rbp, call_wrapper_off   * wordSize);
    const Address result        (rbp, result_off         * wordSize);
    const Address result_type   (rbp, result_type_off    * wordSize);
    const Address method        (rbp, method_off         * wordSize);
    const Address entry_point   (rbp, entry_point_off    * wordSize);
    const Address parameters    (rbp, parameters_off     * wordSize);
    const Address parameter_size(rbp, parameter_size_off * wordSize);

    // same as in generate_catch_exception()!
    const Address thread        (rbp, thread_off         * wordSize);

    const Address r15_save(rbp, r15_off * wordSize);
    const Address r14_save(rbp, r14_off * wordSize);
    const Address r13_save(rbp, r13_off * wordSize);
    const Address r12_save(rbp, r12_off * wordSize);
    const Address rbx_save(rbp, rbx_off * wordSize);

    // stub code
    __ enter();
    __ subptr(rsp, -rsp_after_call_off * wordSize);

    // 将参数从寄存器放入栈中,防止占用寄存器。
    // save register parameters
    __ movptr(parameters,   c_rarg5); // parameters
    __ movptr(entry_point,  c_rarg4); // entry_point
    __ movptr(method,       c_rarg3); // method
    __ movl(result_type,    c_rarg2); // result type
    __ movptr(result,       c_rarg1); // result
    __ movptr(call_wrapper, c_rarg0); // call wrapper

    // 保存callee-saved寄存器。
    // save regs belonging to calling function
    __ movptr(rbx_save, rbx);
    __ movptr(r12_save, r12);
    __ movptr(r13_save, r13);
    __ movptr(r14_save, r14);
    __ movptr(r15_save, r15);
    if (UseAVX > 2) {
        __ movl(rbx, 0xffff);
        __ kmovwl(k1, rbx);
    }
	
    // 不太会用simd拓展,所以不太明白发生了什么,大概还是放入了栈中对应位置吧?
    const Address mxcsr_save(rbp, mxcsr_off * wordSize);
    {
        Label skip_ldmx;
        __ stmxcsr(mxcsr_save);
        __ movl(rax, mxcsr_save);
        __ andl(rax, MXCSR_MASK);    // Only check control and mask bits
        ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
        __ cmp32(rax, mxcsr_std);
        __ jcc(Assembler::equal, skip_ldmx);
        __ ldmxcsr(mxcsr_std);
        __ bind(skip_ldmx);
    }


    // Load up thread register
    __ movptr(r15_thread, thread);
    __ reinit_heapbase();

    // 从JavaArguments的数组中复制对应字数的数据到栈中,这个结构就是通过JNI_ArgumentPusher把可变参数列表转化而来的。
    // pass parameters if any
    BLOCK_COMMENT("pass parameters if any");
    Label parameters_done;
    __ movl(c_rarg3, parameter_size);
    __ testl(c_rarg3, c_rarg3);
    __ jcc(Assembler::zero, parameters_done);

    Label loop;
    __ movptr(c_rarg2, parameters);       // parameter pointer
    __ movl(c_rarg1, c_rarg3);            // parameter counter is in c_rarg1
    __ BIND(loop);
    __ movptr(rax, Address(c_rarg2, 0));// get parameter
    __ addptr(c_rarg2, wordSize);       // advance to next parameter
    __ decrementl(c_rarg1);             // decrement counter
    __ push(rax);                       // pass parameter
    __ jcc(Assembler::notZero, loop);


    // call Java function
    __ BIND(parameters_done);
    __ movptr(rbx, method);             // get Method*
    __ movptr(c_rarg1, entry_point);    // get entry_point
    __ mov(r13, rsp);                   // set sender sp
    BLOCK_COMMENT("call Java function");
    __ call(c_rarg1); // 在这里进入entry_point,实际上是generate_normal_entry生成的代码。

    BLOCK_COMMENT("call_stub_return_address:");
    return_address = __ pc(); // 会成为一个异常返回点,如果发生了无法处理的异常会跳转到这里。

    // 处理返回值,返回值在java方法的栈顶,也就是rax或者xmm0中,x86实现或许有不同。
    // store result depending on type (everything that is not
    // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT)
    __ movptr(c_rarg0, result); // 将返回值保存地址赋给c_rarg0。
    Label is_long, is_float, is_double, exit;
    __ movl(c_rarg1, result_type);
    __ cmpl(c_rarg1, T_OBJECT);
    __ jcc(Assembler::equal, is_long);
    __ cmpl(c_rarg1, T_LONG);
    __ jcc(Assembler::equal, is_long);
    __ cmpl(c_rarg1, T_FLOAT);
    __ jcc(Assembler::equal, is_float);
    __ cmpl(c_rarg1, T_DOUBLE);
    __ jcc(Assembler::equal, is_double);

    // handle T_INT case
    __ movl(Address(c_rarg0, 0), rax); // 返回值放在rax,然后将返回值移动到目标地址。

    __ BIND(exit);

    // pop parameters 恢复到rsp_after_call位置。
    __ lea(rsp, rsp_after_call);


    // 将值恢复到寄存器。
    __ movptr(r15, r15_save);
    __ movptr(r14, r14_save);
    __ movptr(r13, r13_save);
    __ movptr(r12, r12_save);
    __ movptr(rbx, rbx_save);

    __ ldmxcsr(mxcsr_save);

    // restore rsp
    __ addptr(rsp, -rsp_after_call_off * wordSize);

    // return
    __ vzeroupper();
    __ pop(rbp);
    __ ret(0);
	
    // 这里就返回了,后面还有代码是因为更好利用指令缓存,不常用到的代码就放后面了。
    
    // handle return types different from T_INT
    __ BIND(is_long);
    __ movq(Address(c_rarg0, 0), rax);
    __ jmp(exit);

    __ BIND(is_float);
    __ movflt(Address(c_rarg0, 0), xmm0);
    __ jmp(exit);

    __ BIND(is_double);
    __ movdbl(Address(c_rarg0, 0), xmm0);
    __ jmp(exit);

    return start;
}

在正式进入被调用的java函数之前的栈布局和枚举call_stub_layout中描述的一样,除了下面还有传入的参数以外。

标签:__,java,call,result,JVM,off,movptr,save,方法
From: https://www.cnblogs.com/AANA/p/16717548.html

相关文章

  • Java零基础入门学习Day[6]
    JAVAthis关键字java中this关键字的用法:1、当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中的成员变量;2、在构造函数中,通过this可以调用同一类中别......
  • 使用java代码提交flink job 任务
    转:https://blog.csdn.net/pingweicheng/article/details/118223041以下代码是使用java程序客户端提交flinkjob的示例代码packageclient;importorg.apache.flink.api......
  • java如何获取一个文本文件的编码(格式)信息呢?
    转自:http://www.java265.com/JavaJingYan/202110/16350332691561.html 文本文件是我们在windows平台下常用的一种文件格式,这种格式会随着操作系统的语言不同,而出现其......
  • JVM方法调用
    JVM方法调用以下内容基本上是对于[1]的整理和一些补充。下面的内容以x86为例,其他平台下会有所不同,可能有一些内容暂时还不好理解,不过配合其他内容多看几遍应该还是不成问......
  • JVM方法调用——java之间
    Java方法之间解释方法到解释方法进入解释方法到解释方法是最为简单的一种情况,最常见的调用是invokevirtual。有关的代码在TemplateTable::invokevirtual中:voidTemplat......
  • [javascript] js如何获取浏览器的语言
    当想要实现多语种时,需要获取浏览器的当前语言最直接的,就是访问浏览器内置的 navigator.language 属性:varlang=navigator.language 根据你的浏览器的设置,这段代码......
  • Java Stream流
    Java8Stream流编程Stream使用一种类似于SQL语句从数据库查询数据的直观方式来提供对Java集合运算和表达的高阶抽象。得益于Lambda所带来的函数式编程,StreamAPI可......
  • javascript: 复制数组时的深拷贝及浅拷贝(chrome 105.0.5195.125)
    一,js代码:<html><head><metacharset="utf-8"/><title>测试</title></head><body><buttononclick="assignCopy()">无效:变量直接赋值</button><br/><br......
  • Javaweb学习笔记第十弹
    本章存在的意义,大概就是为了回顾一下被遗忘不久的html了HTML:超文本标记语言(不区分大小写,语法较为松散,但建议书写时规范一些)HTML标签由浏览器来解析标签展示图片具体详......
  • 关于VScode中GDB调试和cmake配合使用的方法
    关于VScode调试GDB的说明      Hello,各位看官好,小弟最近在做嵌入式的项目,那么嵌入式的项目有三个很重要的工具,一个就是VScode,一个就是GDB,还有一个就是CMake工具......