Linux平台中Java代码的调用约定是什么?

时间:2017-01-17 09:39:45

标签: java jvm

我们知道调用约定“前六个整数或指针参数在寄存器RDI,RSI,RDX,RCX(Linux内核接口中的R10 [124]:124),R8和R9”中传递给c / Linux平台中的c ++代码基于以下文章。 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) {

}

2 个答案:

答案 0 :(得分:10)

没有指定JVM如何在内部调用Java方法。各种JVM实现可以遵循不同的调用约定。以下是 Linux x64 HotSpot JVM 的工作原理。

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

1。解释器方法条目

每个Java方法都有一个进入解释器的入口点。此条目用于从解释方法跳转到另一个解释方法。

  • 所有参数都在堆栈中传递,从下到上。
  • rbx包含指向Method*结构的指针 - a的内部元数据 方法被召唤。
  • 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崩溃转储时。

答案 1 :(得分:0)

没有特定的规则,因为调用者和被调用者都在JVM的控制之下,因此不需要遵守约定。

特别是在考虑两种方法都已编译为本机代码的情况下,因为这通常在此代码路径成为热点时触发。在这种情况下,调用方法的代码很可能被内联到调用者代码中,从而启用后续代码转换,这将使其变成几乎与您编写的源代码类似的东西。调用方法的内联版本可以引用最初派生调用参数的值或常量,而不是引用参数变量。 (这尤其适用于所有参数都是常量值的示例)

有关详细信息,请参阅Static single assignment formGlobal value numberingSparse conditional constant propagation。只有在对剩余变量进行所有这些更高级别的优化之后,才会将变量分配给寄存器,因此如果它们的变量仍然存在,它就不适用于任何固定方案中的参数。

如果调用没有内联,有几种不同的场景,每种场景可能都有自己的调用约定:

  • 解释执行调用者进入被调用者的执行
  • 解释执行调用者进入已编译的被调用者
  • 没有内联被调用者的本机代码进入被调用者的解释执行
  • 没有内联被调用者的本机代码进入已编译的被调用者