我们知道以下调用约定:“前六个整数或指针参数在寄存器RDI,RSI,RDX,RCX(Linux内核接口中的R10 [17]:124),R8和R9中传递给c / c ++代码”基于以下文章的Linux平台。
https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions

但是,Linux平台中Java代码的调用约定是什么(假设JVM是热点)?以下是示例,哪些寄存器存储四个参数?

protected void caller( ) {
callee(1,"123", 123,1)
}

protected void callee(int a,String b, Integer c,Object d) {

}

最佳答案

没有指定JVM内部如何调用Java方法。各种JVM实现可能遵循不同的调用约定。这是 Linux x64 上的 HotSpot JVM 的工作方式。

  • Java方法可以在解释器中运行,也可以JIT编译。
  • 解释和编译的代码使用不同的调用约定。

  • 1.解释器方法输入

    每个Java方法都有一个进入解释器的入口。此项用于从解释方法跳转到另一解释方法。
  • 所有参数都从底部到顶部在堆栈上传递。
  • rbx包含一个指向Method*结构的指针-内部的元数据
    方法被调用。
  • r13保存sender_sp-调用者方法的堆栈指针。如果使用rsp + 8适配器,则它可能与c2i不同(请参见下文)。

  • HotSpot源代码中的解释器条目的更多详细信息:templateInterpreter_x86_64.cpp

    2.编译后的条目

    编译方法具有其自己的入口点。编译代码通过该条目调用编译方法。
  • 最多在寄存器中传递6个第一个整数参数:rsirdxrcxr8r9rdi。非静态方法将this引用作为rsi中的第一个参数。
  • xmm0 ... xmm7寄存器中最多可传递8个浮点参数。
  • 所有其他参数从上到下在堆栈上传递。

  • 该约定在assembler_x86.hpp中得到了很好的说明:
        |-------------------------------------------------------|
        | c_rarg0   c_rarg1  c_rarg2 c_rarg3 c_rarg4 c_rarg5    |
        |-------------------------------------------------------|
        | rcx       rdx      r8      r9      rdi*    rsi*       | windows (* not a c_rarg)
        | rdi       rsi      rdx     rcx     r8      r9         | solaris/linux
        |-------------------------------------------------------|
        | j_rarg5   j_rarg0  j_rarg1 j_rarg2 j_rarg3 j_rarg4    |
        |-------------------------------------------------------|
    

    您可能会注意到,Java调用约定看起来与C调用约定相似,但是右移了一个参数。这样做是有意避免在调用JNI方法时产生额外的寄存器重排(您知道,JNI方法在方法参数之前附加了JNIEnv*参数)。

    3.适配器

    Java方法可能还有两个入口点:c2ii2c适配器。这些适配器是动态生成的代码,可将编译后的调用约定转换为解释器布局,反之亦然。 с2ii2c入口点分别用于从编译代码中调用解释方法和从解释代码中编译方法。

    P.S. JVM内部调用方法通常并不重要,因为这些只是最终用户不透明的实现细节。而且,即使在较小的JDK更新中,这些细节也可能会更改。但是,我至少知道一种情况,当Java调用约定的知识显得有用时-分析JVM故障转储时。

    08-05 17:57