没有指定JVM内部如何调用Java方法。各种JVM实现可能遵循不同的调用约定。这是它在 Linux x64* 上的 HotSpot JVM中的
工作方式。 *
- Java方法可以在解释器中运行,也可以JIT编译。
- 解释和编译的代码使用不同的调用约定。
1.解释器方法输入
每个Java方法都有一个进入解释器的入口。此项用于从解释方法跳转到另一解释方法。
- 所有参数都从底部到顶部在堆栈上传递。
rbx
包含指向Method*
结构的指针-所调用方法的内部元数据。r13
hold-sender_sp
调用方方法的堆栈指针。它可能与rsp + 8
使用c2i
适配器的情况有所不同(请参阅下文)。
HotSpot源代码中有关解释程序条目的更多详细信息:templateInterpreter_x86_64.cpp。
2.编译后的条目
编译方法具有其自己的入口点。编译代码通过该条目调用编译方法。
- 多达6个第一整数参数在寄存器通过:
rsi
,rdx
,rcx
,r8
,r9
,rdi
。非静态方法将this
引用作为第一个参数rsi
。 - 最多8个浮点参数在
xmm0
…xmm7
寄存器中传递。 - 所有其他参数从顶部到底部在堆栈上传递。
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方法可能还有两个入口点:
c2i和
i2c适配器。这些适配器是动态生成的代码,可将编译后的调用约定转换为解释器布局,反之亦然。
с2i和
i2c入口点分别用于从编译代码调用解释方法和从解释代码编译方法。
PS
JVM内部调用方法通常并不重要,因为这些只是最终用户不透明的实现细节。而且,即使在较小的JDK更新中,这些细节也可能会更改。但是,我至少知道一种情况,当Java调用约定的知识显得有用时-
分析JVM故障转储时。



