java应用程序的启动在在/hotspot/src/share/tools/launcher/java.c的main()函数中,而在虚拟机初始化过程中,将创建并启动Java的Main线程。最后将调用JNIEnv的CallStaticVoidMethod()来执行main方法。
CallStaticVoidMethod()对应的jni函数为jni_CallStaticVoidMethod,定义在/hotspot/src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod()又调用了jni_invoke_static(),jni_invoke_static()通过JavaCalls的call()发起对Java方法的调用
所有来自虚拟机对Java函数的调用最终都将由JavaCalls模块来完成,JavaCalls将通过call_helper()来执行Java方法并返回调用结果,并最终调用StubRoutines::call_stub()来执行Java方法:
1 // do call
2 { JavaCallWrapper link(method, receiver, result, CHECK);
3 { HandleMark hm(thread); // HandleMark used by HandleMarkCleaner
4
5 StubRoutines::call_stub()(
6 (address)&link,
7 // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
8 result_val_address, // see NOTE above (compiler problem)
9 result_type,
10 method(),
11 entry_point,
12 args->parameters(),
13 args->size_of_parameters(),
14 CHECK
15 );
16
17 result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
18 // Preserve oop return value across possible gc points
19 if (oop_result_flag) {
20 thread->set_vm_result((oop) result->get_jobject());
21 }
22 }
23 }
call_stub()定义在/hotspot/src/share/vm/runtime/stubRoutines.h中,实际上返回的就是CallStub函数指针_call_stub_entry,该指针指向call_stub的汇编实现的目标代码指令地址,即call_stub的例程入口。
// Calls to Java
typedef void (*CallStub)(
address link,
intptr_t* result,
BasicType result_type,
methodOopDesc* method,
address entry_point,
intptr_t* parameters,
int size_of_parameters,
TRAPS
);
static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }
在分析call_stub的汇编代码之前,先了解下x86寄存器和栈帧以及函数调用的相关知识。
x86-64的所有寄存器都是与机器字长(数据总线位宽)相同,即64位的,x86-64将x86的8个32位通用寄存器扩展为64位(eax、ebx、ecx、edx、eci、edi、ebp、esp),并且增加了8个新的64位寄存器(r8-r15),在命名方式上,也从”exx”变为”rxx”,但仍保留”exx”进行32位操作,下表描述了各寄存器的命名和作用
此外,还有16个128位的XMM寄存器,分别为xmm0-15,x84-64的寄存器遵循调用约定(Calling Conventions):
1.参数传递:
(1).前4个参数的int类型分别通过rcx、rdx、r8、r9传递,多余的在栈空间上传递(从右向左依次入栈),寄存器所有的参数都是向右对齐的(低位对齐)
(2).浮点数类型的参数通过xmm0-xmm3传递,注意不同类型的参数占用的寄存器序号是根据参数的序号来决定的,比如add(int,double,float,int)就分别保存在rcx、xmm1、xmm2、r9寄存器中
(3).8/16/32/64类型的结构体或共用体和_m64类型将使用rcx、rdx、r8、r9直接传递,而其他类型将会通过指针引用的方式在这4个寄存器中传递
(4).被调用函数当需要时要把寄存器中的参数移动到栈空间中(shadow space)
2.返回值传递
(1).对于可以填充为64位的返回值(包括_m64)将使用rax进行传递
(2).对于_m128(i/d)以及浮点数类型将使用xmm0传递
(3).对于64位以上的返回值,将由调用函数在栈上为其分配空间,并将其指针保存在rcx中作为”第一个参数”,而传入参数将依次右移,最后函数调用完后,由rax返回该空间的指针
(4).用户定义的返回值类型长度必须是1、2、4、8、16、32、64
3.调用者/被调用者保存寄存器
调用者保存寄存器:rax、rcx、rdx、r8-r11都认为是易失型寄存器(volatile),这些寄存器随时可能被用到,这些寄存器将由调用者自行维护,当调用其他函数时,被调用函数对这些寄存器的操作并不会影响调用函数(即这些寄存器的作用范围仅限于当前函数)。
被调用者保存寄存器:rbx、rbp、rdi、rsi、r12-r15、xmm6-xmm15都是非易失型寄存器(non-volatile),调用其他函数时,这些寄存器的值可能在调用返回时还需要用,那么被调用函数就必须将这些寄存器的值保存起来,当要返回时,恢复这些寄存器的值(即这些寄存器的作用范围是跨函数调用的)。
以如下程序为例,分析函数调用的栈帧布局:
1 double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2)
2
3 {
4 int local_i1, local_i2;
5 float local_f1;
6 double local_d1;
7 double local_d2 = 3.0;
8 local_i1 = param_i1;
9 local_i2 = param_i2;
10 local_f1 = param_f1;
11 local_d1 = param_d1;
12 return local_d1 + local_f1 * (local_i2 - local_i1)