JVM字节码使用了一些专业术语,当谈到调用点时,它指的是某个方法(调用者)内的一个位置,在那里另外一个方法(被调用者)被调用了,不仅如此,在非静态方法调用的情况下,我们总是将方法解析到某个对象上。这个对象就是所谓的接收者对象(receiver object),其运行时类型被称为接收者类型(receiver type)。
调用约定定义了程序控制流从一个函数(或方法)转入或转出的应用程序二进制接口(Application Binary Interface,ABI),也就是如何传递参数和返回值,以及如何准备和恢复栈。有时候还需要维护支持调试、异常处理和垃圾回收需求的栈帧信息。
一旦定义了某种语言在某个平台上的调用约定,任何在这个平台上为这种语言生成代码的编译器都应该遵循这个约定。不同语言的代码如果都遵循同一个调用约定的话,彼此之间可以交互。例如之前介绍的解释执行的main()方法调用本地函数时,解释执行将参数从左到右压栈,本地函数的压栈顺序是从右到左,所以才需要生成专门的例程来进行转换,这样就能顺利交互了。
HotSpot VM中大概有3种类型的栈帧,分别为C/C++函数对应的栈帧、Java解释栈和Java编译栈,所以会有对C/C++函数的调用、Java解释执行的调用和Java编译执行的调用,每种调用都需要遵守不同的调用约定,下面详细介绍一下各个调用约定。
1、Java解释执行的调用约定
之前已经详细介绍过Java解释执行,我们归纳总结一下调用相关的约定。
在进行解释执行时,一些寄存器中需要预先存储一些值,如下:
value |
x86_64 |
---|---|
interp. method |
RBX |
interp. arg ptr |
RSP+8 |
interp. saved sp |
RSI |
有些寄存器一般都会保留下来用来保存一些特定的值,如下:
value |
x86_64 |
---|---|
JavaThread |
R15 |
HeapBase |
R12 |
为了方便在程序中使用R12和R15寄存器,HotSpot VM定义了对应的变量,如下:
REGISTER_DECLARATION(Register, r12_heapbase, r12); // callee-saved REGISTER_DECLARATION(Register, r15_thread, r15); // callee-saved
宏定义在register.hpp文件中,如下:
#define REGISTER_DECLARATION(type, name, value) \ extern const type name; \ enum { name##_##type##EnumValue = value##_##type##EnumValue }
宏扩展后如下:
extern const Register r12_heapbase; enum { r12_heapbase_RegisterEnumValue = r12_RegisterEnumValue } extern const Register r15_thread; enum { r15_thread_RegisterEnumValue = r15_RegisterEnumValue }
然后会将对应的值赋值到对应的变量上,如下:
const Register r12_heapbase = ((Register)r12_heapbase_RegisterEnumValue); const Register r15_thread = ((Register)r15_thread_RegisterEnumValue);
当允许进行地址压缩时,R12一般会保存堆的基地址。
value |
x86_64 |
---|---|
interp. java sp |
RSP |
interp. fp |
RBP |
RSP表示当前栈帧的栈顶,而RBP表示当前栈帧的栈底,所以RSP和RBP都有固定的用法,不会参与其它任何计算或传参。
解释执行时,Java虚拟机使用局部变量表来完成方法调用时的参数传递。当调用一个方法时,它的参数会传递到从0开始的连续局部变量表位置上。特别地,当调用一个实例方法时,第0个局部变量一定和来存储被调用的实例方法所在的对象的引用,也就是this关键字表示的对象。需要注意的是,当传递的参数类型为long或double是,需要占用2个slot。对于x86-64位来说,虽然一个slot是8字节,已经通过存储下long或double类型的数值了,但是仍然需要另外一个slot并将此slot设置为NULL。
解释执行的结果会存储到栈顶,并且调用方压入的实参要由调用者来清理,因为被调用方并不一定知道参数的位置和个数。
2、Java编译执行的调用约定
Java编译执行目前还没有介绍,不过我们先介绍一下相关的调用约定。
在进行编译执行时,一些寄存器中需要预先存储一些值,如下:
j_rarg0... j_rarg5共6个寄存器用来传递参数,如int、boolean等参数,需要提示的是long类型的参数也可通过j_rarg来传递,因为对于x84-64们来说,寄存器是8字节大小的,能够用来传递long类型实参。浮点类型的数通过j_farg0...j_farg7这8个寄存器来传递。
j_rarg和j_farg相关寄存器的定义如下:
REGISTER_DECLARATION(Register, j_rarg0, c_rarg1); REGISTER_DECLARATION(Register, j_rarg1, c_rarg2); REGISTER_DECLARATION(Register, j_rarg2, c_rarg3); REGISTER_DECLARATION(Register, j_rarg3, c_rarg4); REGISTER_DECLARATION(Register, j_rarg4, c_rarg5); REGISTER_DECLARATION(Register, j_rarg5, c_rarg0); REGISTER_DECLARATION(XMMRegister, j_farg0, xmm0); REGISTER_DECLARATION(XMMRegister, j_farg1, xmm1); REGISTER_DECLARATION(XMMRegister, j_farg2, xmm2); REGISTER_DECLARATION(XMMRegister, j_farg3, xmm3); REGISTER_DECLARATION(XMMRegister, j_farg4, xmm4); REGISTER_DECLARATION(XMMRegister, j_farg5, xmm5); REGISTER_DECLARATION(XMMRegister, j_farg6, xmm6); REGISTER_DECLARATION(XMMRegister, j_farg7, xmm7);
宏扩展后如下:
extern const Register j_rarg0; enum { j_rarg0_RegisterEnumValue = c_rarg1_RegisterEnumValue } extern const Register j_rarg1; enum { j_rarg1_RegisterEnumValue = c_rarg2_RegisterEnumValue } extern const Register j_rarg2; enum { j_rarg2_RegisterEnumValue = c_rarg3_RegisterEnumValue } extern const Register j_rarg3; enum { j_rarg3_RegisterEnumValue = c_rarg4_RegisterEnumValue } extern const Register j_rarg4; enum { j_rarg4_RegisterEnumValue = c_rarg5_RegisterEnumValue } extern const Register j_rarg5; enum { j_rarg5_RegisterEnumValue = c_rarg0_RegisterEnumValue } extern const XMMRegister j_farg0; enum { j_farg0_XMMRegisterEnumValue = xmm0_XMMRegisterEnumValue } extern const XMMRegister j_farg1; enum { j_farg1_XMMRegisterEnumValue = xmm1_XMMRegisterEnumValue } extern const XMMRegister j_farg2; enum { j_farg2_XMMRegisterEnumValue = xmm2_XMMRegisterEnumValue } extern const XMMRegister j_farg3; enum { j_farg3_XMMRegisterEnumValue = xmm3_XMMRegisterEnumValue } extern const XMMRegister j_farg4; enum { j_farg4_XMMRegisterEnumValue = xmm4_XMMRegisterEnumValue } extern const XMMRegister j_farg5; enum { j_farg5_XMMRegisterEnumValue = xmm5_XMMRegisterEnumValue } extern const XMMRegister j_farg6; enum { j_farg6_XMMRegisterEnumValue = xmm6_XMMRegisterEnumValue } extern const XMMRegister j_farg7; enum { j_farg7_XMMRegisterEnumValue = xmm7_XMMRegisterEnumValue }
将对应的值赋值给对应的变量,如下:
const Register j_rarg0 = ((Register)j_rarg0_RegisterEnumValue); const Register j_rarg1 = ((Register)j_rarg1_RegisterEnumValue); const Register j_rarg2 = ((Register)j_rarg2_RegisterEnumValue); const Register j_rarg3 = ((Register)j_rarg3_RegisterEnumValue); const Register j_rarg4 = ((Register)j_rarg4_RegisterEnumValue); const Register j_rarg5 = ((Register)j_rarg5_RegisterEnumValue); const XMMRegister j_farg0 = ((XMMRegister)j_farg0_XMMRegisterEnumValue); const XMMRegister j_farg1 = ((XMMRegister)j_farg1_XMMRegisterEnumValue); const XMMRegister j_farg2 = ((XMMRegister)j_farg2_XMMRegisterEnumValue); const XMMRegister j_farg3 = ((XMMRegister)j_farg3_XMMRegisterEnumValue); const XMMRegister j_farg4 = ((XMMRegister)j_farg4_XMMRegisterEnumValue); const XMMRegister j_farg5 = ((XMMRegister)j_farg5_XMMRegisterEnumValue); const XMMRegister j_farg6 = ((XMMRegister)j_farg6_XMMRegisterEnumValue); const XMMRegister j_farg7 = ((XMMRegister)j_farg7_XMMRegisterEnumValue);
变量j_rarg和j_farg的前缀j表示Java编译执行时使用的寄存器,如使用j_rarg0传递第1个非浮点类型参数,通过j_farg0传递第1个浮点类型参数等。j_rarg0到j_rarg5对应的寄存器如下:
reg. arg |
int#0 |
int#1 |
int#2 |
int#3 |
int#4 |
int#5 |
float regs |
---|---|---|---|---|---|---|---|
name |
j_rarg0 |
j_rarg1 |
j_rarg2 |
j_rarg3 |
j_rarg4 |
j_rarg5 |
j_farg0...j_farg7 |
Linux/Solaris |
RSI |
RDX |
RCX |
R8 |
R9 |
RDI |
XMM0..XMM7 |
j_farg0到j_farg7对应的是xmm0到xmm7这8个寄存器。
3、native方法的调用约定
这里介绍的是native方法对应的本地函数的调用约定。CPU架构、位数和操作系统共同决定了如何调用,我们在这里只介绍x86架构下64位系统在Linux上的调用约定。
在执行本地函数时,一些寄存器中需要预先存储一些值,如下:
value |
x86_64 |
---|---|
sp alignment |
16 bytes |
long result |
RAX |
int result |
RAX |
native sp |
RSP |
return pc |
RSP(0) |
stack arg #i |
RSP(8+i*8) |
float result |
XMM0 |
reg. int args |
(see below) |
reg. long args |
same as ints |
reg. float args |
(see below) |
在x86-64位系统下,前6个非浮点数的实参通过寄存器传递,前8个浮点数通过寄存器传递,多余出来的参数需要通过栈来传递,而且压力的顺序是从右向左,也就是最后一个参数在最栈底的位置。
用来传递参数的寄存器的约定如下:
reg. arg |
int#0 |
int#1 |
int#2 |
int#3 |
int#4 |
int#5 |
float regs |
---|---|---|---|---|---|---|---|
name |
c_rarg0 |
c_rarg1 |
c_rarg2 |
c_rarg3 |
c_rarg4 |
c_rarg5 |
f_rarg0..f_rarg7 |
Linux/Solaris |
RDI |
RSI |
RDX |
RCX |
R8 |
R9 |
XMM0..XMM7 |
为了方便在程序中使用,我们直接定义了如下变量,通过变量来使用对应的寄存器。
REGISTER_DECLARATION(Register, c_rarg0, rdi); REGISTER_DECLARATION(Register, c_rarg1, rsi); REGISTER_DECLARATION(Register, c_rarg2, rdx); REGISTER_DECLARATION(Register, c_rarg3, rcx); REGISTER_DECLARATION(Register, c_rarg4, r8); REGISTER_DECLARATION(Register, c_rarg5, r9); REGISTER_DECLARATION(XMMRegister, c_farg0, xmm0); REGISTER_DECLARATION(XMMRegister, c_farg1, xmm1); REGISTER_DECLARATION(XMMRegister, c_farg2, xmm2); REGISTER_DECLARATION(XMMRegister, c_farg3, xmm3); REGISTER_DECLARATION(XMMRegister, c_farg4, xmm4); REGISTER_DECLARATION(XMMRegister, c_farg5, xmm5); REGISTER_DECLARATION(XMMRegister, c_farg6, xmm6); REGISTER_DECLARATION(XMMRegister, c_farg7, xmm7);
宏扩展后的结果如下:
extern const Register c_rarg0; enum { c_rarg0_RegisterEnumValue = rdi_RegisterEnumValue } extern const Register c_rarg1; enum { c_rarg1_RegisterEnumValue = rsi_RegisterEnumValue } extern const Register c_rarg2; enum { c_rarg2_RegisterEnumValue = rdx_RegisterEnumValue } extern const Register c_rarg3; enum { c_rarg3_RegisterEnumValue = rcx_RegisterEnumValue } extern const Register c_rarg4; enum { c_rarg4_RegisterEnumValue = r8_RegisterEnumValue } extern const Register c_rarg5; enum { c_rarg5_RegisterEnumValue = r9_RegisterEnumValue } extern const XMMRegister c_farg0; enum { c_farg0_XMMRegisterEnumValue = xmm0_XMMRegisterEnumValue } extern const XMMRegister c_farg1; enum { c_farg1_XMMRegisterEnumValue = xmm1_XMMRegisterEnumValue } extern const XMMRegister c_farg2; enum { c_farg2_XMMRegisterEnumValue = xmm2_XMMRegisterEnumValue } extern const XMMRegister c_farg3; enum { c_farg3_XMMRegisterEnumValue = xmm3_XMMRegisterEnumValue } extern const XMMRegister c_farg4; enum { c_farg4_XMMRegisterEnumValue = xmm4_XMMRegisterEnumValue } extern const XMMRegister c_farg5; enum { c_farg5_XMMRegisterEnumValue = xmm5_XMMRegisterEnumValue } extern const XMMRegister c_farg6; enum { c_farg6_XMMRegisterEnumValue = xmm6_XMMRegisterEnumValue } extern const XMMRegister c_farg7; enum { c_farg7_XMMRegisterEnumValue = xmm7_XMMRegisterEnumValue }
将对应的值赋值给对应的变量,如下:
const Register c_rarg0 = ((Register)c_rarg0_RegisterEnumValue); const Register c_rarg1 = ((Register)c_rarg1_RegisterEnumValue); const Register c_rarg2 = ((Register)c_rarg2_RegisterEnumValue); const Register c_rarg3 = ((Register)c_rarg3_RegisterEnumValue); const Register c_rarg4 = ((Register)c_rarg4_RegisterEnumValue); const Register c_rarg5 = ((Register)c_rarg5_RegisterEnumValue); const XMMRegister c_farg0 = ((XMMRegister)c_farg0_XMMRegisterEnumValue); const XMMRegister c_farg1 = ((XMMRegister)c_farg1_XMMRegisterEnumValue); const XMMRegister c_farg2 = ((XMMRegister)c_farg2_XMMRegisterEnumValue); const XMMRegister c_farg3 = ((XMMRegister)c_farg3_XMMRegisterEnumValue); const XMMRegister c_farg4 = ((XMMRegister)c_farg4_XMMRegisterEnumValue); const XMMRegister c_farg5 = ((XMMRegister)c_farg5_XMMRegisterEnumValue); const XMMRegister c_farg6 = ((XMMRegister)c_farg6_XMMRegisterEnumValue); const XMMRegister c_farg7 = ((XMMRegister)c_farg7_XMMRegisterEnumValue);
编译执行Java方法与执行本地函数时,都会通过6个寄存器传递参数,调用约定为:
也就是说,Java编译执行时,也是通过C/C++函数使用的那6个寄存器来传递参数的,只是用来规定传递参数的寄存器顺序不同,如Java的第1个参数使用%rsi寄存器,而C/C++函数使用%rdi来传递。这样的规定是有原因的,假设现在Java编译执行调用native实例方法,那么native方法的本地函数实现会多出一个参数JNIEnv*,那么我们只需要使用%rdi存储即可,如果%rdi中存储着Java方法的参数,需要保存到栈中,而其它的寄存器不需要移动。
如果是native静态方法,那么寄存器其实还是需要移动的,因为native静态方法对应的本地函数会多出来2个参数JNIEnv*和jclass。
公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流